diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c02efeac..71d935854 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,17 @@ set(Impacto_Src src/profile/games/cc/backlogmenu.cpp src/profile/games/cc/dialoguebox.cpp src/profile/games/cc/titlemenu.cpp + src/profile/games/sg0/titlemenu.cpp + src/profile/games/sg0/savemenu.cpp + src/profile/games/sg0/sysmesbox.cpp + src/profile/games/sg0/phone.cpp + src/profile/games/sg0/systemmenu.cpp + src/profile/games/sg0/helpmenu.cpp + src/profile/games/sg0/backlogmenu.cpp + src/profile/games/sg0/tipsmenu.cpp + src/profile/games/sg0/optionsmenu.cpp + src/profile/games/sg0/librarymenu.cpp + src/profile/games/sg0/clearlistmenu.cpp src/profile/games/cc/sysmesbox.cpp src/profile/games/cclcc/systemmenu.cpp @@ -212,6 +223,19 @@ set(Impacto_Src src/games/cc/backlogmenu.cpp src/games/cc/titlemenu.cpp + src/games/sg0/titlemenu.cpp + src/games/sg0/savemenu.cpp + src/games/sg0/sysmesbox.cpp + src/games/sg0/phone.cpp + src/games/sg0/rine.cpp + src/games/sg0/systemmenu.cpp + src/games/sg0/helpmenu.cpp + src/games/sg0/backlogmenu.cpp + src/games/sg0/tipsmenu.cpp + src/games/sg0/optionsmenu.cpp + src/games/sg0/librarymenu.cpp + src/games/sg0/clearlistmenu.cpp + src/games/sg0/cutin.cpp src/games/cc/sysmesbox.cpp src/games/cclcc/commonmenu.cpp @@ -522,6 +546,17 @@ set(Impacto_Header src/profile/games/cc/backlogmenu.h src/profile/games/cc/dialoguebox.h src/profile/games/cc/titlemenu.h + src/profile/games/sg0/titlemenu.h + src/profile/games/sg0/savemenu.h + src/profile/games/sg0/sysmesbox.h + src/profile/games/sg0/phone.h + src/profile/games/sg0/systemmenu.h + src/profile/games/sg0/helpmenu.h + src/profile/games/sg0/backlogmenu.h + src/profile/games/sg0/tipsmenu.h + src/profile/games/sg0/optionsmenu.h + src/profile/games/sg0/librarymenu.h + src/profile/games/sg0/clearlistmenu.h src/profile/games/cc/sysmesbox.h src/profile/games/cclcc/titlemenu.h @@ -586,6 +621,20 @@ set(Impacto_Header src/games/chlcc/introsequence.h src/games/cc/titlemenu.h + src/games/sg0/titlemenu.h + src/games/sg0/savemenu.h + src/games/sg0/sysmesbox.h + src/games/sg0/phone.h + src/games/sg0/rine.h + src/games/sg0/systemmenu.h + src/games/sg0/helpmenu.h + src/games/sg0/backlogmenu.h + src/games/sg0/tipsmenu.h + src/games/sg0/optionsmenu.h + src/games/sg0/librarymenu.h + src/games/sg0/clearlistmenu.h + src/games/sg0/cutin.h + src/games/sg0/fullwidth.h src/games/cc/sysmesbox.h src/games/cclcc/commonmenu.h diff --git a/portfiles/ffmpeg/0001-create-lib-libraries.patch b/portfiles/ffmpeg/0001-create-lib-libraries.patch new file mode 100644 index 000000000..ced7ba86b --- /dev/null +++ b/portfiles/ffmpeg/0001-create-lib-libraries.patch @@ -0,0 +1,27 @@ +diff --git a/configure b/configure +index 1f0b9497cb..3243e23021 100644 +--- a/configure ++++ b/configure +@@ -5697,17 +5697,19 @@ case $target_os in + ;; + win32|win64) + disable symver +- if enabled shared; then ++# if enabled shared; then + # Link to the import library instead of the normal static library + # for shared libs. + LD_LIB='%.lib' + # Cannot build both shared and static libs with MSVC or icl. +- disable static +- fi ++# disable static ++# fi + ! enabled small && test_cmd $windres --version && enable gnu_windres + enabled x86_32 && check_ldflags -LARGEADDRESSAWARE + add_cppflags -DWIN32_LEAN_AND_MEAN + shlibdir_default="$bindir_default" ++ LIBPREF="" ++ LIBSUF=".lib" + SLIBPREF="" + SLIBSUF=".dll" + SLIBNAME_WITH_VERSION='$(SLIBPREF)$(FULLNAME)-$(LIBVERSION)$(SLIBSUF)' diff --git a/portfiles/ffmpeg/0002-fix-msvc-link.patch b/portfiles/ffmpeg/0002-fix-msvc-link.patch new file mode 100644 index 000000000..c9aa7e752 --- /dev/null +++ b/portfiles/ffmpeg/0002-fix-msvc-link.patch @@ -0,0 +1,11 @@ +diff --git a/configure b/configure +--- a/configure ++++ b/configure +@@ -6162,6 +6162,7 @@ EOF + test -n "$extern_prefix" && append X86ASMFLAGS "-DPREFIX" + case "$objformat" in + elf*) enabled debug && append X86ASMFLAGS $x86asm_debug ;; ++ win*) enabled debug && append X86ASMFLAGS "-g" ;; + esac + + enabled avx512 && check_x86asm avx512_external "vmovdqa32 [eax]{k1}{z}, zmm0" diff --git a/portfiles/ffmpeg/0003-fix-windowsinclude.patch b/portfiles/ffmpeg/0003-fix-windowsinclude.patch new file mode 100644 index 000000000..8b2e22b47 --- /dev/null +++ b/portfiles/ffmpeg/0003-fix-windowsinclude.patch @@ -0,0 +1,13 @@ +diff --git a/fftools/cmdutils.c b/fftools/cmdutils.c +--- a/fftools/cmdutils.c ++++ b/fftools/cmdutils.c +@@ -51,6 +51,8 @@ + #include "fopen_utf8.h" + #include "opt_common.h" + #ifdef _WIN32 ++#define _WIN32_WINNT 0x0502 ++#define WIN32_LEAN_AND_MEAN + #include + #include "compat/w32dlfcn.h" + #endif + diff --git a/portfiles/ffmpeg/0004-dependencies.patch b/portfiles/ffmpeg/0004-dependencies.patch new file mode 100644 index 000000000..f1f6e72be --- /dev/null +++ b/portfiles/ffmpeg/0004-dependencies.patch @@ -0,0 +1,65 @@ +diff --git a/configure b/configure +index a8b74e0..c99f41c 100755 +--- a/configure ++++ b/configure +@@ -6633,7 +6633,7 @@ fi + + enabled zlib && { check_pkg_config zlib zlib "zlib.h" zlibVersion || + check_lib zlib zlib.h zlibVersion -lz; } +-enabled bzlib && check_lib bzlib bzlib.h BZ2_bzlibVersion -lbz2 ++enabled bzlib && require_pkg_config bzlib bzip2 bzlib.h BZ2_bzlibVersion + enabled lzma && check_lib lzma lzma.h lzma_version_number -llzma + + enabled zlib && test_exec $zlib_extralibs <= 3.98.3" lame/lame.h lame_set_VBR_quality -lmp3lame $libm_extralibs ++enabled libmp3lame && { check_lib libmp3lame lame/lame.h lame_set_VBR_quality -lmp3lame $libm_extralibs || ++ require libmp3lame lame/lame.h lame_set_VBR_quality -llibmp3lame-static -llibmpghip-static $libm_extralibs; } + enabled libmysofa && { check_pkg_config libmysofa libmysofa mysofa.h mysofa_neighborhood_init_withstepdefine || + require libmysofa mysofa.h mysofa_neighborhood_init_withstepdefine -lmysofa $zlib_extralibs; } + enabled libnpp && { check_lib libnpp npp.h nppGetLibVersion -lnppig -lnppicc -lnppc -lnppidei -lnppif || +@@ -6772,7 +6773,7 @@ require_pkg_config libopencv opencv opencv/cxcore.h cvCreateImageHeader; } + enabled libopenh264 && require_pkg_config libopenh264 "openh264 >= 1.3.0" wels/codec_api.h WelsGetCodecVersion + enabled libopenjpeg && { check_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version || + { require_pkg_config libopenjpeg "libopenjp2 >= 2.1.0" openjpeg.h opj_version -DOPJ_STATIC && add_cppflags -DOPJ_STATIC; } } +-enabled libopenmpt && require_pkg_config libopenmpt "libopenmpt >= 0.2.6557" libopenmpt/libopenmpt.h openmpt_module_create -lstdc++ && append libopenmpt_extralibs "-lstdc++" ++enabled libopenmpt && require_pkg_config libopenmpt "libopenmpt >= 0.2.6557" libopenmpt/libopenmpt.h openmpt_module_create + enabled libopenvino && { { check_pkg_config libopenvino openvino openvino/c/openvino.h ov_core_create && enable openvino2; } || + { check_pkg_config libopenvino openvino c_api/ie_c_api.h ie_c_api_version || + require libopenvino c_api/ie_c_api.h ie_c_api_version -linference_engine_c_api; } } +@@ -6796,8 +6797,8 @@ enabled libshaderc && require_pkg_config spirv_compiler "shaderc >= 2019. + enabled libshine && require_pkg_config libshine shine shine/layer3.h shine_encode_buffer + enabled libsmbclient && { check_pkg_config libsmbclient smbclient libsmbclient.h smbc_init || + require libsmbclient libsmbclient.h smbc_init -lsmbclient; } +-enabled libsnappy && require libsnappy snappy-c.h snappy_compress -lsnappy -lstdc++ +-enabled libsoxr && require libsoxr soxr.h soxr_create -lsoxr ++enabled libsnappy && require_pkg_config libsnappy snappy snappy-c.h snappy_compress ++enabled libsoxr && require libsoxr soxr.h soxr_create -lsoxr $libm_extralibs + enabled libssh && require_pkg_config libssh "libssh >= 0.6.0" libssh/sftp.h sftp_init + enabled libspeex && require_pkg_config libspeex speex speex/speex.h speex_decoder_init + enabled libsrt && require_pkg_config libsrt "srt >= 1.3.0" srt/srt.h srt_socket +@@ -6880,6 +6881,8 @@ enabled openal && { check_pkg_config openal "openal >= 1.1" "AL/al.h" + enabled opencl && { check_pkg_config opencl OpenCL CL/cl.h clEnqueueNDRangeKernel || + check_lib opencl OpenCL/cl.h clEnqueueNDRangeKernel "-framework OpenCL" || + check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL || ++ check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL -lAdvapi32 -lOle32 -lCfgmgr32|| ++ check_lib opencl CL/cl.h clEnqueueNDRangeKernel -lOpenCL -pthread -ldl || + die "ERROR: opencl not found"; } && + { test_cpp_condition "OpenCL/cl.h" "defined(CL_VERSION_1_2)" || + test_cpp_condition "CL/cl.h" "defined(CL_VERSION_1_2)" || +@@ -7204,10 +7207,10 @@ enabled amf && + "(AMF_VERSION_MAJOR << 48 | AMF_VERSION_MINOR << 32 | AMF_VERSION_RELEASE << 16 | AMF_VERSION_BUILD_NUM) >= 0x0001000400210000" + + # Funny iconv installations are not unusual, so check it after all flags have been set +-if enabled libc_iconv; then ++if enabled libc_iconv && disabled iconv; then + check_func_headers iconv.h iconv + elif enabled iconv; then +- check_func_headers iconv.h iconv || check_lib iconv iconv.h iconv -liconv ++ check_func_headers iconv.h iconv || check_lib iconv iconv.h iconv -liconv || check_lib iconv iconv.h iconv -liconv -lcharset + fi + + enabled debug && add_cflags -g"$debuglevel" && add_asflags -g"$debuglevel" diff --git a/portfiles/ffmpeg/0005-fix-nasm.patch b/portfiles/ffmpeg/0005-fix-nasm.patch new file mode 100644 index 000000000..68b7503b2 --- /dev/null +++ b/portfiles/ffmpeg/0005-fix-nasm.patch @@ -0,0 +1,78 @@ +diff --git a/libavcodec/x86/mlpdsp.asm b/libavcodec/x86/mlpdsp.asm +index 3dc641e..609b834 100644 +--- a/libavcodec/x86/mlpdsp.asm ++++ b/libavcodec/x86/mlpdsp.asm +@@ -23,7 +23,9 @@ + + SECTION .text + +-%if ARCH_X86_64 ++%ifn ARCH_X86_64 ++mlpdsp_placeholder: times 4 db 0 ++%else + + %macro SHLX 2 + %if cpuflag(bmi2) +diff --git a/libavcodec/x86/proresdsp.asm b/libavcodec/x86/proresdsp.asm +index 65c9fad..5ad73f3 100644 +--- a/libavcodec/x86/proresdsp.asm ++++ b/libavcodec/x86/proresdsp.asm +@@ -24,7 +24,10 @@ + + %include "libavutil/x86/x86util.asm" + +-%if ARCH_X86_64 ++%ifn ARCH_X86_64 ++SECTION .rdata ++proresdsp_placeholder: times 4 db 0 ++%else + + SECTION_RODATA + +diff --git a/libavcodec/x86/vvc/vvc_mc.asm b/libavcodec/x86/vvc/vvc_mc.asm +index 30aa97c..3975f98 100644 +--- a/libavcodec/x86/vvc/vvc_mc.asm ++++ b/libavcodec/x86/vvc/vvc_mc.asm +@@ -31,7 +31,9 @@ + + SECTION_RODATA 32 + +-%if ARCH_X86_64 ++%ifn ARCH_X86_64 ++vvc_mc_placeholder: times 4 db 0 ++%else + + %if HAVE_AVX2_EXTERNAL + +diff --git a/libavfilter/x86/vf_atadenoise.asm b/libavfilter/x86/vf_atadenoise.asm +index 4945ad3..748b65a 100644 +--- a/libavfilter/x86/vf_atadenoise.asm ++++ b/libavfilter/x86/vf_atadenoise.asm +@@ -20,7 +20,10 @@ + ;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + ;****************************************************************************** + +-%if ARCH_X86_64 ++%ifn ARCH_X86_64 ++SECTION .rdata ++vf_atadenoise_placeholder: times 4 db 0 ++%else + + %include "libavutil/x86/x86util.asm" + +diff --git a/libavfilter/x86/vf_nlmeans.asm b/libavfilter/x86/vf_nlmeans.asm +index 8f57801..9aef3a4 100644 +--- a/libavfilter/x86/vf_nlmeans.asm ++++ b/libavfilter/x86/vf_nlmeans.asm +@@ -21,7 +21,10 @@ + + %include "libavutil/x86/x86util.asm" + +-%if HAVE_AVX2_EXTERNAL && ARCH_X86_64 ++%ifn HAVE_AVX2_EXTERNAL && ARCH_X86_64 ++SECTION .rdata ++vf_nlmeans_placeholder: times 4 db 0 ++%else + + SECTION_RODATA 32 + diff --git a/portfiles/ffmpeg/0007-fix-lib-naming.patch b/portfiles/ffmpeg/0007-fix-lib-naming.patch new file mode 100644 index 000000000..c22f9c199 --- /dev/null +++ b/portfiles/ffmpeg/0007-fix-lib-naming.patch @@ -0,0 +1,12 @@ +diff --git a/configure b/configure +index d6c4388..75b96c3 100644 +--- a/configure ++++ b/configure +@@ -4781,6 +4781,7 @@ msvc_common_flags(){ + -mfp16-format=*) ;; + -lz) echo zlib.lib ;; + -lx264) echo libx264.lib ;; ++ -lmp3lame) echo libmp3lame.lib ;; + -lstdc++) ;; + -l*) echo ${flag#-l}.lib ;; + -LARGEADDRESSAWARE) echo $flag ;; diff --git a/portfiles/ffmpeg/0013-define-WINVER.patch b/portfiles/ffmpeg/0013-define-WINVER.patch new file mode 100644 index 000000000..295a738e7 --- /dev/null +++ b/portfiles/ffmpeg/0013-define-WINVER.patch @@ -0,0 +1,15 @@ +diff --color -Naur src_old/libavcodec/mf_utils.c src/libavcodec/mf_utils.c +--- src_old/libavcodec/mf_utils.c 2020-07-11 05:26:17.000000000 +0700 ++++ src/libavcodec/mf_utils.c 2020-11-13 12:55:57.226976400 +0700 +@@ -22,6 +22,11 @@ + #define _WIN32_WINNT 0x0602 + #endif + ++#if !defined(WINVER) || WINVER < 0x0602 ++#undef WINVER ++#define WINVER 0x0602 ++#endif ++ + #include "mf_utils.h" + #include "libavutil/pixdesc.h" + diff --git a/portfiles/ffmpeg/0020-fix-aarch64-libswscale.patch b/portfiles/ffmpeg/0020-fix-aarch64-libswscale.patch new file mode 100644 index 000000000..f47e82ed8 --- /dev/null +++ b/portfiles/ffmpeg/0020-fix-aarch64-libswscale.patch @@ -0,0 +1,28 @@ +diff --git a/libswscale/aarch64/yuv2rgb_neon.S b/libswscale/aarch64/yuv2rgb_neon.S +index 89d69e7f6c..4bc1607a7a 100644 +--- a/libswscale/aarch64/yuv2rgb_neon.S ++++ b/libswscale/aarch64/yuv2rgb_neon.S +@@ -169,19 +169,19 @@ function ff_\ifmt\()_to_\ofmt\()_neon, export=1 + sqdmulh v26.8h, v26.8h, v0.8h // ((Y1*(1<<3) - y_offset) * y_coeff) >> 15 + sqdmulh v27.8h, v27.8h, v0.8h // ((Y2*(1<<3) - y_offset) * y_coeff) >> 15 + +-.ifc \ofmt,argb // 1 2 3 0 ++.ifc \ofmt,argb + compute_rgba v5.8b,v6.8b,v7.8b,v4.8b, v17.8b,v18.8b,v19.8b,v16.8b + .endif + +-.ifc \ofmt,rgba // 0 1 2 3 ++.ifc \ofmt,rgba + compute_rgba v4.8b,v5.8b,v6.8b,v7.8b, v16.8b,v17.8b,v18.8b,v19.8b + .endif + +-.ifc \ofmt,abgr // 3 2 1 0 ++.ifc \ofmt,abgr + compute_rgba v7.8b,v6.8b,v5.8b,v4.8b, v19.8b,v18.8b,v17.8b,v16.8b + .endif + +-.ifc \ofmt,bgra // 2 1 0 3 ++.ifc \ofmt,bgra + compute_rgba v6.8b,v5.8b,v4.8b,v7.8b, v18.8b,v17.8b,v16.8b,v19.8b + .endif + diff --git a/portfiles/ffmpeg/0024-fix-osx-host-c11.patch b/portfiles/ffmpeg/0024-fix-osx-host-c11.patch new file mode 100644 index 000000000..dbce2f53b --- /dev/null +++ b/portfiles/ffmpeg/0024-fix-osx-host-c11.patch @@ -0,0 +1,15 @@ +diff --git a/configure b/configure +index 4f5353f84b..dd9147c677 100755 +--- a/configure ++++ b/configure +@@ -5607,8 +5607,8 @@ check_cppflags -D_FILE_OFFSET_BITS=64 + check_cppflags -D_LARGEFILE_SOURCE + + add_host_cppflags -D_ISOC11_SOURCE + check_host_cflags_cc -std=$stdc ctype.h "__STDC_VERSION__ >= 201112L" || +- check_host_cflags_cc -std=c11 ctype.h "__STDC_VERSION__ >= 201112L" || die "Host compiler lacks C11 support" ++ check_host_cflags_cc -std=c11 ctype.h "__STDC_VERSION__ >= 201112L" + + check_host_cflags -Wall + check_host_cflags $host_cflags_speed + diff --git a/portfiles/ffmpeg/0040-ffmpeg-add-av_stream_get_first_dts-for-chromium.patch b/portfiles/ffmpeg/0040-ffmpeg-add-av_stream_get_first_dts-for-chromium.patch new file mode 100644 index 000000000..c2e1d8ff0 --- /dev/null +++ b/portfiles/ffmpeg/0040-ffmpeg-add-av_stream_get_first_dts-for-chromium.patch @@ -0,0 +1,35 @@ +diff --git a/libavformat/avformat.h b/libavformat/avformat.h +index cd7b0d941c..b4a6dce885 100644 +--- a/libavformat/avformat.h ++++ b/libavformat/avformat.h +@@ -1169,7 +1169,11 @@ typedef struct AVStreamGroup { + } AVStreamGroup; + + struct AVCodecParserContext *av_stream_get_parser(const AVStream *s); + ++// Chromium: We use the internal field first_dts vvv ++int64_t av_stream_get_first_dts(const AVStream *st); ++// Chromium: We use the internal field first_dts ^^^ ++ + #define AV_PROGRAM_RUNNING 1 + + /** +diff --git a/libavformat/mux_utils.c b/libavformat/mux_utils.c +index de7580c32d..0ef0fe530e 100644 +--- a/libavformat/mux_utils.c ++++ b/libavformat/mux_utils.c +@@ -29,7 +29,14 @@ #include "avformat.h" + #include "avio.h" + #include "internal.h" + #include "mux.h" + ++// Chromium: We use the internal field first_dts vvv ++int64_t av_stream_get_first_dts(const AVStream *st) ++{ ++ return cffstream(st)->first_dts; ++} ++// Chromium: We use the internal field first_dts ^^^ ++ + int avformat_query_codec(const AVOutputFormat *ofmt, enum AVCodecID codec_id, + int std_compliance) + { diff --git a/portfiles/ffmpeg/0041-add-const-for-opengl-definition.patch b/portfiles/ffmpeg/0041-add-const-for-opengl-definition.patch new file mode 100644 index 000000000..b22b40d1f --- /dev/null +++ b/portfiles/ffmpeg/0041-add-const-for-opengl-definition.patch @@ -0,0 +1,13 @@ +diff --git a/libavdevice/opengl_enc.c b/libavdevice/opengl_enc.c +index b2ac6eb..6351614 100644 +--- a/libavdevice/opengl_enc.c ++++ b/libavdevice/opengl_enc.c +@@ -116,7 +116,7 @@ typedef void (APIENTRY *FF_PFNGLATTACHSHADERPROC) (GLuint program, GLuint shad + typedef GLuint (APIENTRY *FF_PFNGLCREATESHADERPROC) (GLenum type); + typedef void (APIENTRY *FF_PFNGLDELETESHADERPROC) (GLuint shader); + typedef void (APIENTRY *FF_PFNGLCOMPILESHADERPROC) (GLuint shader); +-typedef void (APIENTRY *FF_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* *string, const GLint *length); ++typedef void (APIENTRY *FF_PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const char* const *string, const GLint *length); + typedef void (APIENTRY *FF_PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); + typedef void (APIENTRY *FF_PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, char *infoLog); + diff --git a/portfiles/ffmpeg/0042-fix-arm64-linux.patch b/portfiles/ffmpeg/0042-fix-arm64-linux.patch new file mode 100644 index 000000000..6ff63c371 --- /dev/null +++ b/portfiles/ffmpeg/0042-fix-arm64-linux.patch @@ -0,0 +1,9 @@ +diff --git a/ffbuild/libversion.sh b/ffbuild/libversion.sh +index a94ab58..ecaa90c 100644 +--- a/ffbuild/libversion.sh ++++ b/ffbuild/libversion.sh +@@ -1,3 +1,4 @@ ++#!/bin/sh + toupper(){ + echo "$@" | tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ + } diff --git a/portfiles/ffmpeg/0043-fix-miss-head.patch b/portfiles/ffmpeg/0043-fix-miss-head.patch new file mode 100644 index 000000000..bad42798c --- /dev/null +++ b/portfiles/ffmpeg/0043-fix-miss-head.patch @@ -0,0 +1,12 @@ +diff --git a/libavfilter/textutils.c b/libavfilter/textutils.c +index ef658d0..c61b0ad 100644 +--- a/libavfilter/textutils.c ++++ b/libavfilter/textutils.c +@@ -31,6 +31,7 @@ + #include "libavutil/file.h" + #include "libavutil/mem.h" + #include "libavutil/time.h" ++#include "libavutil/time_internal.h" + + static int ff_expand_text_function_internal(FFExpandTextContext *expand_text, AVBPrint *bp, + char *name, unsigned argc, char **argv) diff --git a/portfiles/ffmpeg/0044-fix-vulkan-debug-callback-abi.patch b/portfiles/ffmpeg/0044-fix-vulkan-debug-callback-abi.patch new file mode 100644 index 000000000..d80e468a6 --- /dev/null +++ b/portfiles/ffmpeg/0044-fix-vulkan-debug-callback-abi.patch @@ -0,0 +1,13 @@ +diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c +index 6317ab7d0e..f1f70e9b96 100644 +--- a/libavutil/hwcontext_vulkan.c ++++ b/libavutil/hwcontext_vulkan.c +@@ -453,7 +453,7 @@ static const VulkanOptExtension optional_device_exts[] = { + { VK_KHR_VIDEO_DECODE_AV1_EXTENSION_NAME, FF_VK_EXT_VIDEO_DECODE_AV1 }, + }; + +-static VkBool32 VKAPI_CALL vk_dbg_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, ++static VKAPI_ATTR VkBool32 VKAPI_CALL vk_dbg_callback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT *data, + void *priv) diff --git a/portfiles/ffmpeg/1000-bink2-decoder.patch b/portfiles/ffmpeg/1000-bink2-decoder.patch new file mode 100644 index 000000000..95710788f --- /dev/null +++ b/portfiles/ffmpeg/1000-bink2-decoder.patch @@ -0,0 +1,3865 @@ +diff --git a/configure b/configure +index 98b582a5d5..89ac08f611 100755 +--- a/configure ++++ b/configure +@@ -2919,6 +2919,7 @@ atrac3p_decoder_select="sinewin" + atrac3pal_decoder_select="sinewin" + av1_decoder_select="atsc_a53 cbs_av1 dovi_rpudec" + bink_decoder_select="blockdsp hpeldsp" ++bink2_decoder_select="blockdsp" + binkaudio_dct_decoder_select="wma_freqs" + binkaudio_rdft_decoder_select="wma_freqs" + cavs_decoder_select="blockdsp golomb h264chroma idctdsp qpeldsp videodsp" +diff --git a/libavcodec/Makefile b/libavcodec/Makefile +index 153a9e3881..ebff3c239a 100644 +--- a/libavcodec/Makefile ++++ b/libavcodec/Makefile +@@ -277,6 +277,7 @@ OBJS-$(CONFIG_AVUI_ENCODER) += avuienc.o + OBJS-$(CONFIG_BETHSOFTVID_DECODER) += bethsoftvideo.o + OBJS-$(CONFIG_BFI_DECODER) += bfi.o + OBJS-$(CONFIG_BINK_DECODER) += bink.o binkdsp.o ++OBJS-$(CONFIG_BINK2_DECODER) += bink2.o + OBJS-$(CONFIG_BINKAUDIO_DCT_DECODER) += binkaudio.o + OBJS-$(CONFIG_BINKAUDIO_RDFT_DECODER) += binkaudio.o + OBJS-$(CONFIG_BINTEXT_DECODER) += bintext.o cga_data.o +diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c +index aa0fc47647..0b806a1458 100644 +--- a/libavcodec/allcodecs.c ++++ b/libavcodec/allcodecs.c +@@ -63,6 +63,7 @@ extern const FFCodec ff_avui_decoder; + extern const FFCodec ff_bethsoftvid_decoder; + extern const FFCodec ff_bfi_decoder; + extern const FFCodec ff_bink_decoder; ++extern const FFCodec ff_bink2_decoder; + extern const FFCodec ff_bitpacked_decoder; + extern const FFCodec ff_bitpacked_encoder; + extern const FFCodec ff_bmp_encoder; +diff --git a/libavcodec/bink2.c b/libavcodec/bink2.c +new file mode 100644 +index 0000000000..6519efa335 +--- /dev/null ++++ b/libavcodec/bink2.c +@@ -0,0 +1,480 @@ ++/* ++ * Bink video 2 decoder ++ * Copyright (c) 2014 Konstantin Shishkov ++ * Copyright (c) 2019 Paul B Mahol ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include "libavutil/avassert.h" ++#include "libavutil/attributes.h" ++#include "libavutil/emms.h" ++#include "libavutil/imgutils.h" ++#include "libavutil/internal.h" ++#include "libavutil/mem.h" ++#include "avcodec.h" ++#include "blockdsp.h" ++#include "codec_internal.h" ++#include "copy_block.h" ++#include "decode.h" ++#include "idctdsp.h" ++#include "internal.h" ++#include "mathops.h" ++ ++#define BITSTREAM_READER_LE ++#include "get_bits.h" ++#include "unary.h" ++#include "bink2.h" ++ ++#include "bink2f.h" ++#include "bink2g.h" ++ ++static void bink2_get_block_flags(GetBitContext *gb, int offset, int size, uint8_t *dst) ++{ ++ int flags_left, mode = 0; ++ unsigned flag = 0; ++ ++ if (get_bits1(gb) == 0) { ++ for (int i = 0; i < size; i++, offset++) { ++ const unsigned value = get_bits1(gb); ++ ++ dst[offset >> 3] |= value << (offset & 7); ++ } ++ ++ return; ++ } ++ ++ flags_left = size; ++ while (flags_left > 0) { ++ if (get_bits1(gb) == 0) { ++ int count; ++ ++ if (mode == 3) { ++ flag ^= 1; ++ } else { ++ flag = get_bits1(gb); ++ } ++ ++ dst[offset >> 3] |= flag << (offset & 7); ++ offset++; ++ flags_left--; ++ ++ count = FFMIN(flags_left, 4); ++ for (int i = 0; i < count; i++, offset++, flags_left--) { ++ const unsigned value = get_bits1(gb); ++ ++ dst[offset >> 3] |= value << (offset & 7); ++ } ++ mode = 2; ++ } else { ++ int bits, run; ++ ++ bits = flags_left < 4 ? 2 : flags_left < 16 ? 4 : 5; ++ if (mode == 3) { ++ flag ^= 1; ++ run = bits + 1; ++ } else { ++ flag = get_bits1(gb); ++ run = bits + 2; ++ } ++ ++ run = FFMIN(run, flags_left); ++ if (run != flags_left) { ++ const unsigned extra = get_bits(gb, bits); ++ ++ run += extra; ++ mode = extra == (1U << bits) - 1 ? 1 : 3; ++ } ++ ++ for (int i = 0; i < run; i++, offset++) ++ dst[offset >> 3] |= flag << (offset & 7); ++ flags_left -= run; ++ } ++ } ++} ++ ++typedef struct Bink2ThreadData { ++ AVFrame *frame; ++ int is_kf; ++ int slice_end[BINK2_MAX_SLICES]; ++} Bink2ThreadData; ++ ++static int bink2_decode_slice(AVCodecContext *avctx, void *arg, ++ int jobnr, int threadnr) ++{ ++ Bink2Context *parent = avctx->priv_data; ++ Bink2ThreadData *td = arg; ++ Bink2Context *c = parent->slice_ctx[jobnr]; ++ GetBitContext *gb = &c->gb; ++ AVFrame *frame = td->frame; ++ uint8_t *dst[4]; ++ uint8_t *src[4]; ++ int stride[4]; ++ int sstride[4]; ++ const int start = jobnr ? parent->slice_height[jobnr - 1] : 0; ++ const int end = parent->slice_height[jobnr]; ++ int bits_left, ret; ++ ++ (void)threadnr; ++ ++ for (int i = 0; i < 4; i++) { ++ src[i] = parent->last->data[i]; ++ dst[i] = frame->data[i]; ++ stride[i] = frame->linesize[i]; ++ sstride[i] = parent->last->linesize[i]; ++ } ++ ++ dst[0] += start * stride[0]; ++ dst[1] += start / 2 * stride[1]; ++ dst[2] += start / 2 * stride[2]; ++ if (c->has_alpha) ++ dst[3] += start * stride[3]; ++ ++ if (c->version <= 'f' || !(c->frame_flags & 0x2000)) ++ ret = bink2f_decode_slice(c, dst, stride, src, sstride, ++ td->is_kf, start, end); ++ else ++ ret = bink2g_decode_slice(c, dst, stride, src, sstride, ++ td->is_kf, start, end); ++ if (ret < 0) ++ return ret; ++ ++ align_get_bits(gb); ++ bits_left = 8 * td->slice_end[jobnr] - get_bits_count(gb); ++ if (bits_left < 0) ++ av_log(avctx, AV_LOG_WARNING, "slice %d: overread\n", jobnr); ++ else if (bits_left > 24) ++ av_log(avctx, AV_LOG_WARNING, "slice %d: underread %d\n", ++ jobnr, bits_left); ++ ++ return 0; ++} ++ ++static int bink2_decode_frame(AVCodecContext *avctx, AVFrame *frame, ++ int *got_frame, AVPacket *pkt) ++{ ++ Bink2Context * const c = avctx->priv_data; ++ GetBitContext *gb = &c->gb; ++ Bink2ThreadData td = { .frame = frame }; ++ int thread_ret[BINK2_MAX_SLICES] = { 0 }; ++ int is_kf = !!(pkt->flags & AV_PKT_FLAG_KEY); ++ int first_slice_bit, ret, w, h; ++ int height_a; ++ ++ if (pkt->size < 4) ++ return AVERROR_INVALIDDATA; ++ ++ w = avctx->width; ++ h = avctx->height; ++ ret = ff_set_dimensions(avctx, FFALIGN(w, 32), FFALIGN(h, 32)); ++ if (ret < 0) ++ return ret; ++ avctx->width = w; ++ avctx->height = h; ++ ++ if ((ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF)) < 0) ++ return ret; ++ ++ if (!is_kf && (!c->last->data[0] || ++ !c->last->data[1] || ++ !c->last->data[2])) ++ return AVERROR_INVALIDDATA; ++ ++ c->frame_flags = AV_RL32(pkt->data); ++ ff_dlog(avctx, "frame flags %X\n", c->frame_flags); ++ ++ if ((ret = init_get_bits8(gb, pkt->data, pkt->size)) < 0) ++ return ret; ++ ++ height_a = (avctx->height + 31) & 0xFFFFFFE0; ++ if (c->version <= 'f') { ++ c->num_slices = 2; ++ c->slice_height[0] = (avctx->height / 2 + 16) & 0xFFFFFFE0; ++ } else if (c->version == 'g') { ++ if (height_a < 128) { ++ c->num_slices = 1; ++ } else { ++ c->num_slices = 2; ++ c->slice_height[0] = (avctx->height / 2 + 16) & 0xFFFFFFE0; ++ } ++ } else { ++ int start, end; ++ ++ c->num_slices = kb2h_num_slices[c->flags & 3]; ++ start = 0; ++ end = height_a + 32 * c->num_slices - 1; ++ for (int i = 0; i < c->num_slices - 1; i++) { ++ start += ((end - start) / (c->num_slices - i)) & 0xFFFFFFE0; ++ end -= 32; ++ c->slice_height[i] = start; ++ } ++ } ++ c->slice_height[c->num_slices - 1] = height_a; ++ ++ if (pkt->size < 4 + 4 * (c->num_slices - 1)) ++ return AVERROR_INVALIDDATA; ++ ++ skip_bits_long(gb, 32 + 32 * (c->num_slices - 1)); ++ ++ memset(c->row_cbp, 0, ++ ((((avctx->height + 31) >> 3) + 7) >> 3) * sizeof(*c->row_cbp)); ++ memset(c->col_cbp, 0, ++ ((((avctx->width + 31) >> 3) + 7) >> 3) * sizeof(*c->col_cbp)); ++ ++ if (c->frame_flags & 0x10000) { ++ if (!(c->frame_flags & 0x8000)) ++ bink2_get_block_flags(gb, 1, (((avctx->height + 15) & ~15) >> 3) - 1, c->row_cbp); ++ if (!(c->frame_flags & 0x4000)) ++ bink2_get_block_flags(gb, 1, (((avctx->width + 31) & ~31) >> 3) - 1, c->col_cbp); ++ } ++ ++ if (get_bits_left(gb) < 0) ++ return AVERROR_INVALIDDATA; ++ ++ td.is_kf = is_kf; ++ first_slice_bit = get_bits_count(gb); ++ for (int i = 0; i < c->num_slices; i++) { ++ Bink2Context *sc = c->slice_ctx[i]; ++ const int start = i ? td.slice_end[i - 1] : 0; ++ const int start_bit = i ? 8 * start : first_slice_bit; ++ ++ td.slice_end[i] = i == c->num_slices - 1 ? ++ pkt->size : AV_RL32(pkt->data + 4 + 4 * i); ++ if (td.slice_end[i] < start || ++ td.slice_end[i] > pkt->size || ++ 8 * td.slice_end[i] < start_bit) ++ return AVERROR_INVALIDDATA; ++ ++ sc->frame_flags = c->frame_flags; ++ if (i) { ++ if ((ret = init_get_bits8(&sc->gb, pkt->data, pkt->size)) < 0) ++ return ret; ++ skip_bits_long(&sc->gb, start_bit); ++ } ++ } ++ ++ if ((ret = avctx->execute2(avctx, bink2_decode_slice, &td, ++ thread_ret, c->num_slices)) < 0) ++ return ret; ++ for (int i = 0; i < c->num_slices; i++) ++ if (thread_ret[i] < 0) ++ return thread_ret[i]; ++ ++ if (c->version > 'f' && (c->frame_flags & 0x2000)) { ++ bink2g_postprocess_frame(c, frame->data, frame->linesize); ++ } ++ ++ if (is_kf) ++ frame->flags |= AV_FRAME_FLAG_KEY; ++ else ++ frame->flags &= ~AV_FRAME_FLAG_KEY; ++ frame->pict_type = is_kf ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_P; ++ ++ av_frame_unref(c->last); ++ if ((ret = av_frame_ref(c->last, frame)) < 0) ++ return ret; ++ ++ frame->colorspace = avctx->colorspace; ++ frame->color_range = avctx->color_range; ++ ++ *got_frame = 1; ++ ++ /* always report that the buffer was completely consumed */ ++ return pkt->size; ++} ++ ++#define INIT_VLC_STATIC_LE(vlc, nb_bits, nb_codes, \ ++ bits, bits_wrap, bits_size, \ ++ codes, codes_wrap, codes_size, \ ++ symbols, symbols_wrap, symbols_size, \ ++ static_size) \ ++ do { \ ++ static VLCElem table[static_size]; \ ++ (vlc)->table = table; \ ++ (vlc)->table_allocated = static_size; \ ++ ff_vlc_init_sparse(vlc, nb_bits, nb_codes, \ ++ bits, bits_wrap, bits_size, \ ++ codes, codes_wrap, codes_size, \ ++ symbols, symbols_wrap, symbols_size, \ ++ VLC_INIT_LE | VLC_INIT_USE_STATIC); \ ++ } while (0) ++ ++static int bink2_alloc_slice_state(Bink2Context *c, int width) ++{ ++ const int mb_width = (width + 31) / 32; ++ ++ c->current_q = av_malloc_array(mb_width, sizeof(*c->current_q)); ++ c->prev_q = av_malloc_array(mb_width, sizeof(*c->prev_q)); ++ c->current_dc = av_malloc_array(mb_width, sizeof(*c->current_dc)); ++ c->prev_dc = av_malloc_array(mb_width, sizeof(*c->prev_dc)); ++ c->current_idc = av_malloc_array(mb_width, sizeof(*c->current_idc)); ++ c->prev_idc = av_malloc_array(mb_width, sizeof(*c->prev_idc)); ++ c->current_mv = av_malloc_array(mb_width, sizeof(*c->current_mv)); ++ c->prev_mv = av_malloc_array(mb_width, sizeof(*c->prev_mv)); ++ ++ if (!c->current_q || !c->prev_q || ++ !c->current_dc || !c->prev_dc || ++ !c->current_idc || !c->prev_idc || ++ !c->current_mv || !c->prev_mv) ++ return AVERROR(ENOMEM); ++ ++ return 0; ++} ++ ++static void bink2_free_slice_state(Bink2Context *c) ++{ ++ av_freep(&c->current_q); ++ av_freep(&c->prev_q); ++ av_freep(&c->current_dc); ++ av_freep(&c->prev_dc); ++ av_freep(&c->current_idc); ++ av_freep(&c->prev_idc); ++ av_freep(&c->current_mv); ++ av_freep(&c->prev_mv); ++} ++ ++static av_cold int bink2_decode_init(AVCodecContext *avctx) ++{ ++ Bink2Context * const c = avctx->priv_data; ++ int ret; ++ ++ c->version = avctx->codec_tag >> 24; ++ if (avctx->extradata_size < 4) { ++ av_log(avctx, AV_LOG_ERROR, "Extradata missing or too short\n"); ++ return AVERROR_INVALIDDATA; ++ } ++ c->flags = AV_RL32(avctx->extradata); ++ av_log(avctx, AV_LOG_DEBUG, "flags: 0x%X\n", c->flags); ++ c->has_alpha = c->flags & BINK_FLAG_ALPHA; ++ c->avctx = avctx; ++ c->slice_ctx[0] = c; ++ ++ c->last = av_frame_alloc(); ++ if (!c->last) ++ return AVERROR(ENOMEM); ++ ++ if ((ret = av_image_check_size(avctx->width, avctx->height, 0, avctx)) < 0) ++ return ret; ++ ++ avctx->pix_fmt = c->has_alpha ? AV_PIX_FMT_YUVA420P : AV_PIX_FMT_YUV420P; ++ avctx->colorspace = AVCOL_SPC_SMPTE170M; ++ avctx->color_range = c->flags & BINK_FLAG_YCRCB_NEW ? ++ AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; ++ ++ ff_blockdsp_init(&c->dsp); ++ ++ INIT_VLC_STATIC_LE(&bink2f_quant_vlc, 9, FF_ARRAY_ELEMS(bink2f_quant_codes), ++ bink2f_quant_bits, 1, 1, bink2f_quant_codes, 1, 1, NULL, 0, 0, 512); ++ INIT_VLC_STATIC_LE(&bink2f_ac_val0_vlc, 9, FF_ARRAY_ELEMS(bink2f_ac_val_bits[0]), ++ bink2f_ac_val_bits[0], 1, 1, bink2f_ac_val_codes[0], 2, 2, NULL, 0, 0, 512); ++ INIT_VLC_STATIC_LE(&bink2f_ac_val1_vlc, 9, FF_ARRAY_ELEMS(bink2f_ac_val_bits[1]), ++ bink2f_ac_val_bits[1], 1, 1, bink2f_ac_val_codes[1], 2, 2, NULL, 0, 0, 512); ++ INIT_VLC_STATIC_LE(&bink2f_ac_skip0_vlc, 9, FF_ARRAY_ELEMS(bink2f_ac_skip_bits[0]), ++ bink2f_ac_skip_bits[0], 1, 1, bink2f_ac_skip_codes[0], 2, 2, NULL, 0, 0, 512); ++ INIT_VLC_STATIC_LE(&bink2f_ac_skip1_vlc, 9, FF_ARRAY_ELEMS(bink2f_ac_skip_bits[1]), ++ bink2f_ac_skip_bits[1], 1, 1, bink2f_ac_skip_codes[1], 2, 2, NULL, 0, 0, 512); ++ ++ INIT_VLC_STATIC_LE(&bink2g_ac_skip0_vlc, 9, FF_ARRAY_ELEMS(bink2g_ac_skip_bits[0]), ++ bink2g_ac_skip_bits[0], 1, 1, bink2g_ac_skip_codes[0], 2, 2, NULL, 0, 0, 512); ++ INIT_VLC_STATIC_LE(&bink2g_ac_skip1_vlc, 9, FF_ARRAY_ELEMS(bink2g_ac_skip_bits[1]), ++ bink2g_ac_skip_bits[1], 1, 1, bink2g_ac_skip_codes[1], 2, 2, NULL, 0, 0, 512); ++ INIT_VLC_STATIC_LE(&bink2g_mv_vlc, 9, FF_ARRAY_ELEMS(bink2g_mv_bits), ++ bink2g_mv_bits, 1, 1, bink2g_mv_codes, 1, 1, NULL, 0, 0, 512); ++ ++ if ((ret = bink2_alloc_slice_state(c, avctx->width)) < 0) ++ return ret; ++ ++ { ++ const size_t mb_count = (size_t)((avctx->width + 31) >> 5) * ++ ((avctx->height + 31) >> 5); ++ ++ c->frame_idc = av_malloc_array(mb_count, sizeof(*c->frame_idc)); ++ c->frame_mv = av_malloc_array(mb_count, sizeof(*c->frame_mv)); ++ if (!c->frame_idc || !c->frame_mv) ++ return AVERROR(ENOMEM); ++ } ++ ++ c->col_cbp = av_calloc((((avctx->width + 31) >> 3) + 7) >> 3, sizeof(*c->col_cbp)); ++ if (!c->col_cbp) ++ return AVERROR(ENOMEM); ++ ++ c->row_cbp = av_calloc((((avctx->height + 31) >> 3) + 7) >> 3, sizeof(*c->row_cbp)); ++ if (!c->row_cbp) ++ return AVERROR(ENOMEM); ++ ++ for (int i = 1; i < BINK2_MAX_SLICES; i++) { ++ Bink2Context *sc = av_mallocz(sizeof(*sc)); ++ ++ if (!sc) ++ return AVERROR(ENOMEM); ++ c->slice_ctx[i] = sc; ++ sc->avctx = avctx; ++ sc->version = c->version; ++ sc->has_alpha = c->has_alpha; ++ sc->flags = c->flags; ++ sc->dsp = c->dsp; ++ sc->col_cbp = c->col_cbp; ++ sc->row_cbp = c->row_cbp; ++ sc->frame_idc = c->frame_idc; ++ sc->frame_mv = c->frame_mv; ++ if ((ret = bink2_alloc_slice_state(sc, avctx->width)) < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void bink2_flush(AVCodecContext *avctx) ++{ ++ Bink2Context *c = avctx->priv_data; ++ ++ av_frame_unref(c->last); ++} ++ ++static av_cold int bink2_decode_end(AVCodecContext *avctx) ++{ ++ Bink2Context * const c = avctx->priv_data; ++ ++ av_frame_free(&c->last); ++ for (int i = 1; i < BINK2_MAX_SLICES; i++) { ++ if (c->slice_ctx[i]) { ++ bink2_free_slice_state(c->slice_ctx[i]); ++ av_freep(&c->slice_ctx[i]); ++ } ++ } ++ bink2_free_slice_state(c); ++ av_freep(&c->frame_idc); ++ av_freep(&c->frame_mv); ++ av_freep(&c->col_cbp); ++ av_freep(&c->row_cbp); ++ ++ return 0; ++} ++ ++const FFCodec ff_bink2_decoder = { ++ .p.name = "binkvideo2", ++ .p.long_name = NULL_IF_CONFIG_SMALL("Bink video 2"), ++ .p.type = AVMEDIA_TYPE_VIDEO, ++ .p.id = AV_CODEC_ID_BINKVIDEO2, ++ .priv_data_size = sizeof(Bink2Context), ++ .init = bink2_decode_init, ++ .close = bink2_decode_end, ++ FF_CODEC_DECODE_CB(bink2_decode_frame), ++ .flush = bink2_flush, ++ .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_SLICE_THREADS, ++ .caps_internal = FF_CODEC_CAP_INIT_CLEANUP, ++}; +diff --git a/libavcodec/bink2.h b/libavcodec/bink2.h +new file mode 100644 +index 0000000000..67d7603370 +--- /dev/null ++++ b/libavcodec/bink2.h +@@ -0,0 +1,147 @@ ++/* ++ * Bink video 2 decoder ++ * Copyright (c) 2014 Konstantin Shishkov ++ * Copyright (c) 2019 Paul B Mahol ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#ifndef AVCODEC_BINK2_H ++#define AVCODEC_BINK2_H ++ ++#include ++#include "libavutil/mem_internal.h" ++#include "avcodec.h" ++#include "blockdsp.h" ++#include "copy_block.h" ++#include "unary.h" ++#include "get_bits.h" ++ ++static VLC bink2f_quant_vlc; ++static VLC bink2f_ac_val0_vlc; ++static VLC bink2f_ac_val1_vlc; ++static VLC bink2f_ac_skip0_vlc; ++static VLC bink2f_ac_skip1_vlc; ++static VLC bink2g_ac_skip0_vlc; ++static VLC bink2g_ac_skip1_vlc; ++static VLC bink2g_mv_vlc; ++ ++static const uint8_t kb2h_num_slices[] = { ++ 2, 3, 4, 8, ++}; ++ ++static const uint8_t luma_repos[] = { ++ 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15, ++}; ++ ++static const uint8_t dq_patterns[8] = { 8, 0, 1, 0, 2, 0, 1, 0 }; ++ ++static const uint8_t bink2_next_skips[] = { ++ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, ++}; ++ ++#define NUM_AC_SKIPS 14 ++#define BINK2_MAX_SLICES 8 ++#define BINK_FLAG_YCRCB_NEW 0x00000010 ++#define BINK_FLAG_ALPHA 0x00100000 ++#define DC_MPRED(A, B, C) FFMIN(FFMAX((C) + (B) - (A), FFMIN3(A, B, C)), FFMAX3(A, B, C)) ++#define DC_MPRED2(A, B) FFMIN(FFMAX((A), (B)), FFMAX(FFMIN((A), (B)), 2 * (A) - (B))) ++#define LHFILTER(src) (((((src)[0]+(src)[1])*19 >> 1)-((src)[-1]+(src)[2 ])*2+(((src)[-2 ]+(src)[3 ])>>1)+8)>>4) ++#define LVFILTER(src, i) (((((src)[0]+(src)[i])*19 >> 1)-((src)[-i]+(src)[2*i])*2+(((src)[-2*i]+(src)[3*i])>>1)+8)>>4) ++ ++typedef struct QuantPredict { ++ int8_t intra_q; ++ int8_t inter_q; ++} QuantPredict; ++ ++typedef struct DCPredict { ++ float dc[4][16]; ++ int block_type; ++} DCPredict; ++ ++typedef struct DCIPredict { ++ int dc[4][16]; ++ uint16_t state[4][16]; ++ int block_type; ++} DCIPredict; ++ ++typedef struct MVectors { ++ int v[4][2]; ++ int nb_vectors; ++} MVectors; ++ ++typedef struct MVPredict { ++ MVectors mv; ++} MVPredict; ++ ++/* ++ * Decoder context ++ */ ++typedef struct Bink2Context { ++ AVCodecContext *avctx; ++ GetBitContext gb; ++ BlockDSPContext dsp; ++ AVFrame *last; ++ int version; ///< internal Bink file version ++ int has_alpha; ++ ++ DECLARE_ALIGNED(16, float, block[4][64]); ++ DECLARE_ALIGNED(16, int16_t, iblock[4][64]); ++ ++ QuantPredict *current_q; ++ QuantPredict *prev_q; ++ ++ DCPredict *current_dc; ++ DCPredict *prev_dc; ++ ++ DCIPredict *current_idc; ++ DCIPredict *prev_idc; ++ ++ MVPredict *current_mv; ++ MVPredict *prev_mv; ++ ++ /* Modern Bink2 reconstruction and filtering need the neighbouring rows ++ * even when those rows belong to different bitstream slices. Slice jobs ++ * write disjoint rows here; the parent context post-processes the complete ++ * frame after every slice has finished. */ ++ DCIPredict *frame_idc; ++ MVPredict *frame_mv; ++ ++ uint8_t *col_cbp; ++ uint8_t *row_cbp; ++ ++ int num_slices; ++ int slice_height[BINK2_MAX_SLICES]; ++ struct Bink2Context *slice_ctx[BINK2_MAX_SLICES]; ++ ++ int comp; ++ int mb_pos; ++ unsigned flags; ++ unsigned frame_flags; ++} Bink2Context; ++ ++/** ++ * Bink2 video block types ++ */ ++enum BlockTypes { ++ INTRA_BLOCK = 0, ///< intra DCT block ++ SKIP_BLOCK, ///< skipped block ++ MOTION_BLOCK, ///< block is copied from previous frame with some offset ++ RESIDUE_BLOCK, ///< motion block with some difference added ++}; ++ ++#endif /* AVCODEC_BINK2_H */ +diff --git a/libavcodec/bink2f.h b/libavcodec/bink2f.h +new file mode 100644 +index 0000000000..4fa42d0b71 +--- /dev/null ++++ b/libavcodec/bink2f.h +@@ -0,0 +1,1241 @@ ++/* ++ * Bink video 2 decoder ++ * Copyright (c) 2014 Konstantin Shishkov ++ * Copyright (c) 2019 Paul B Mahol ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#ifndef AVCODEC_BINK2F_H ++#define AVCODEC_BINK2F_H ++ ++#include ++#include "avcodec.h" ++#include "get_bits.h" ++#include "bink2.h" ++ ++static const uint8_t bink2f_quant_codes[16] = { ++ 0x01, 0x02, 0x04, 0x08, 0x10, 0x30, 0x50, 0x70, ++ 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0, ++}; ++ ++static const uint8_t bink2f_quant_bits[16] = { ++ 1, 2, 3, 4, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, ++}; ++ ++static const uint16_t bink2f_ac_val_codes[2][13] = { ++ { ++ 0x04, 0x01, 0x02, 0x00, 0x08, 0x18, 0xF8, 0x178, 0x138, ++ 0x38, 0x1B8, 0x78, 0xB8 ++ }, ++ { ++ 0x0A, 0x01, 0x04, 0x08, 0x06, 0x00, 0x02, 0x1A, 0x2A, ++ 0x16A, 0x1EA, 0x6A, 0xEA ++ }, ++}; ++ ++static const uint8_t bink2f_ac_val_bits[2][13] = { ++ { 3, 1, 2, 4, 5, 6, 8, 9, 9, 9, 9, 9, 9 }, ++ { 6, 1, 3, 4, 3, 4, 4, 5, 7, 9, 9, 9, 9 }, ++}; ++ ++static const uint16_t bink2f_ac_skip_codes[2][NUM_AC_SKIPS] = { ++ { ++ 0x00, 0x01, 0x0D, 0x15, 0x45, 0x85, 0xA5, 0x165, ++ 0x65, 0x1E5, 0xE5, 0x25, 0x03, 0x05 ++ }, ++ { ++ 0x00, 0x01, 0x03, 0x07, 0x1F, 0x1B, 0x0F, 0x2F, ++ 0x5B, 0xDB, 0x1DB, 0x3B, 0x05, 0x0B ++ } ++}; ++ ++static const uint8_t bink2f_ac_skip_bits[2][NUM_AC_SKIPS] = { ++ { 1, 3, 4, 5, 7, 8, 8, 9, 9, 9, 9, 8, 2, 8 }, ++ { 1, 3, 4, 4, 5, 7, 6, 6, 8, 9, 9, 6, 3, 5 } ++}; ++ ++static const uint8_t bink2f_skips[] = { ++ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 62, 0, 0, 0, ++}; ++ ++static const float bink2f_dc_quant[16] = { ++ 4, 4, 4, 4, 4, 6, 7, 8, 10, 12, 16, 24, 32, 48, 64, 128 ++}; ++ ++static const float bink2f_ac_quant[16] = { ++ 1.0, 2.0, 2.5, 3.0, 3.5, 4.0, 6.0, 7.0, 8.0, 12.0, 16.0, 24.0, 32.0, 48.0, 64.0, 128.0 ++}; ++ ++static const float bink2f_luma_intra_qmat[64] = { ++ 0.125, 0.190718, 0.16332, 0.235175, 0.3, 0.392847, 0.345013, 0.210373, ++ 0.208056, 0.288582, 0.317145, 0.387359, 0.450788, 0.790098, 0.562995, 0.263095, ++ 0.228649, 0.294491, 0.341421, 0.460907, 0.653281, 0.731424, 0.60988, 0.252336, ++ 0.205778, 0.346585, 0.422498, 0.501223, 0.749621, 1.004719, 0.636379, 0.251428, ++ 0.225, 0.381436, 0.604285, 0.823113, 0.85, 1.070509, 0.69679, 0.265553, ++ 0.235708, 0.476783, 0.70576, 0.739104, 0.795516, 0.802512, 0.600616, 0.249289, ++ 0.331483, 0.600528, 0.689429, 0.692062, 0.69679, 0.643138, 0.43934, 0.188511, ++ 0.248309, 0.440086, 0.42807, 0.397419, 0.386259, 0.270966, 0.192244, 0.094199, ++}; ++ ++static const float bink2f_luma_inter_qmat[64] = { ++ 0.125, 0.17338, 0.16332, 0.146984, 0.128475, 0.106393, 0.077046, 0.043109, ++ 0.17338, 0.240485, 0.226532, 0.203873, 0.1782, 0.147571, 0.109474, 0.062454, ++ 0.16332, 0.226532, 0.219321, 0.202722, 0.181465, 0.149711, 0.112943, 0.062584, ++ 0.146984, 0.203873, 0.202722, 0.201647, 0.183731, 0.153976, 0.11711, 0.065335, ++ 0.128475, 0.1782, 0.181465, 0.183731, 0.177088, 0.155499, 0.120267, 0.068016, ++ 0.106393, 0.147571, 0.149711, 0.153976, 0.155499, 0.145756, 0.116636, 0.068495, ++ 0.077046, 0.109474, 0.112943, 0.11711, 0.120267, 0.116636, 0.098646, 0.060141, ++ 0.043109, 0.062454, 0.062584, 0.065335, 0.068016, 0.068495, 0.060141, 0.038853, ++}; ++ ++static const float bink2f_chroma_qmat[64] = { ++ 0.125, 0.17338, 0.217761, 0.383793, 0.6875, 0.54016501, 0.37207201, 0.18968099, ++ 0.17338, 0.28056601, 0.32721299, 0.74753499, 0.95358998, 0.74923098, 0.51607901, 0.26309499, ++ 0.217761, 0.32721299, 0.66387498, 1.056244, 0.89826202, 0.70576, 0.48613599, 0.24783, ++ 0.383793, 0.74753499, 1.056244, 0.95059502, 0.80841398, 0.635167, 0.437511, 0.223041, ++ 0.6875, 0.95358998, 0.89826202, 0.80841398, 0.6875, 0.54016501, 0.37207201, 0.18968099, ++ 0.54016501, 0.74923098, 0.70576, 0.635167, 0.54016501, 0.42440501, 0.292335, 0.149031, ++ 0.37207201, 0.51607901, 0.48613599, 0.437511, 0.37207201, 0.292335, 0.201364, 0.102655, ++ 0.18968099, 0.26309499, 0.24783, 0.223041, 0.18968099, 0.149031, 0.102655, 0.052333001 ++}; ++ ++static const uint8_t bink2f_luma_scan[64] = { ++ 0, 2, 1, 8, 9, 17, 10, 16, ++ 24, 3, 18, 25, 32, 11, 33, 26, ++ 4, 40, 19, 12, 27, 41, 34, 5, ++ 20, 48, 6, 28, 15, 42, 23, 35, ++ 21, 13, 14, 7, 31, 43, 49, 36, ++ 22, 56, 39, 50, 30, 44, 29, 51, ++ 57, 47, 58, 59, 63, 61, 55, 38, ++ 52, 62, 45, 37, 60, 46, 54, 53 ++}; ++ ++static const uint8_t bink2f_chroma_scan[64] = { ++ 0, 1, 8, 2, 9, 16, 10, 17, ++ 3, 24, 11, 18, 25, 13, 14, 4, ++ 15, 5, 6, 7, 12, 19, 20, 21, ++ 22, 23, 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, 52, 53, 54, 55, ++ 56, 57, 58, 59, 60, 61, 62, 63 ++}; ++ ++static inline void bink2f_idct_1d(float *blk, int step) ++{ ++ float t00 = blk[2 * step] + blk[6 * step]; ++ float t01 = (blk[2 * step] - blk[6 * step]) * 1.4142135f - t00; ++ float t02 = blk[0 * step] + blk[4 * step]; ++ float t03 = blk[0 * step] - blk[4 * step]; ++ float t04 = blk[3 * step] + blk[5 * step]; ++ float t05 = blk[3 * step] - blk[5 * step]; ++ float t06 = blk[1 * step] + blk[7 * step]; ++ float t07 = blk[1 * step] - blk[7 * step]; ++ float t08 = t02 + t00; ++ float t09 = t02 - t00; ++ float t10 = t03 + t01; ++ float t11 = t03 - t01; ++ float t12 = t06 + t04; ++ float t13 = (t06 - t04) * 1.4142135f; ++ float t14 = (t07 - t05) * 1.847759f; ++ float t15 = t05 * 2.613126f + t14 - t12; ++ float t16 = t13 - t15; ++ float t17 = t07 * 1.0823922f - t14 + t16; ++ ++ blk[0*step] = t08 + t12; ++ blk[1*step] = t10 + t15; ++ blk[2*step] = t11 + t16; ++ blk[3*step] = t09 - t17; ++ blk[4*step] = t09 + t17; ++ blk[5*step] = t11 - t16; ++ blk[6*step] = t10 - t15; ++ blk[7*step] = t08 - t12; ++} ++ ++static void bink2f_idct_put(uint8_t *dst, int stride, float *block) ++{ ++ block[0] += 512.f; ++ ++ for (int i = 0; i < 8; i++) ++ bink2f_idct_1d(block + i, 8); ++ for (int i = 0; i < 8; i++) { ++ bink2f_idct_1d(block, 1); ++ for (int j = 0; j < 8; j++) ++ dst[j] = av_clip_uint8(lrintf(block[j] - 512.0f)); ++ block += 8; ++ dst += stride; ++ } ++} ++ ++static void bink2f_idct_add(uint8_t *dst, int stride, ++ float *block) ++{ ++ block[0] += 512.f; ++ ++ for (int i = 0; i < 8; i++) ++ bink2f_idct_1d(block + i, 8); ++ for (int i = 0; i < 8; i++) { ++ bink2f_idct_1d(block, 1); ++ for (int j = 0; j < 8; j++) ++ dst[j] = av_clip_uint8(dst[j] + lrintf(block[j] - 512.0f)); ++ block += 8; ++ dst += stride; ++ } ++} ++ ++static int bink2f_decode_delta_q(GetBitContext *gb) ++{ ++ int dq = get_vlc2(gb, bink2f_quant_vlc.table, bink2f_quant_vlc.bits, 1); ++ ++ if (dq < 0) ++ return AVERROR_INVALIDDATA; ++ if (dq && get_bits1(gb)) ++ dq = -dq; ++ ++ return dq; ++} ++ ++static unsigned bink2f_decode_cbp_luma(GetBitContext *gb, unsigned prev_cbp) ++{ ++ unsigned cbp, cbp4, cbplo, cbphi; ++ ++ if (get_bits1(gb)) { ++ if (get_bits1(gb)) ++ return prev_cbp; ++ cbplo = prev_cbp & 0xFFFF; ++ } else { ++ cbplo = 0; ++ cbp4 = (prev_cbp >> 4) & 0xF; ++ for (int i = 0; i < 4; i++) { ++ if (!get_bits1(gb)) ++ cbp4 = get_bits(gb, 4); ++ cbplo |= cbp4 << (i * 4); ++ } ++ } ++ cbphi = 0; ++ cbp = cbplo; ++ cbp4 = prev_cbp >> 20 & 0xF; ++ for (int i = 0; i < 4; i++) { ++ if (av_popcount(cbp & 0xF)) { ++ if (av_popcount(cbp & 0xF) == 1) { ++ cbp4 = 0; ++ for (int j = 1; j < 16; j <<= 1) { ++ if ((j & cbp) && get_bits1(gb)) ++ cbp4 |= j; ++ } ++ } else if (!get_bits1(gb)) { ++ cbp4 = 0; ++ for (int j = 1; j < 16; j <<= 1) { ++ if ((j & cbp) && get_bits1(gb)) ++ cbp4 |= j; ++ } ++ } ++ } else { ++ cbp4 = 0; ++ } ++ cbp4 &= cbp; ++ cbphi = (cbphi >> 4) | (cbp4 << 0x1c); ++ cbp >>= 4; ++ } ++ return cbphi | cbplo; ++} ++ ++static unsigned bink2f_decode_cbp_chroma(GetBitContext *gb, unsigned prev_cbp) ++{ ++ unsigned cbplo, cbphi; ++ ++ if (get_bits1(gb)) { ++ if (get_bits1(gb)) ++ return prev_cbp; ++ cbplo = prev_cbp & 0xF; ++ } else { ++ cbplo = get_bits(gb, 4); ++ } ++ ++ cbphi = 0; ++ if (av_popcount(cbplo & 0xF)) { ++ if (av_popcount(cbplo & 0xF) != 1) { ++ cbphi = (prev_cbp >> 16) & cbplo; ++ if (get_bits1(gb)) ++ return cbplo | (cbphi << 16); ++ } ++ cbphi = 0; ++ for (int j = 1; j < 16; j <<= 1) { ++ if ((j & cbplo) && get_bits1(gb)) ++ cbphi |= j; ++ } ++ } ++ return cbplo | (cbphi << 16); ++} ++ ++static const uint8_t q_dc_bits[16] = { ++ 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5, 6, ++}; ++ ++static void bink2f_predict_dc(Bink2Context *c, ++ int is_luma, float mindc, float maxdc, ++ int flags, float tdc[16]) ++{ ++ float *LTdc = c->prev_dc[FFMAX(c->mb_pos - 1, 0)].dc[c->comp]; ++ float *Tdc = c->prev_dc[c->mb_pos].dc[c->comp]; ++ float *Ldc = c->current_dc[FFMAX(c->mb_pos - 1, 0)].dc[c->comp]; ++ float *dc = c->current_dc[c->mb_pos].dc[c->comp]; ++ ++ if (is_luma && (flags & 0x20) && (flags & 0x80)) { ++ dc[0] = av_clipf((mindc < 0 ? 0 : 1024.f) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clipf(DC_MPRED2(dc[1], dc[3]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clipf(dc[4] + tdc[5], mindc, maxdc); ++ dc[6] = av_clipf(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clipf(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clipf(DC_MPRED2(dc[2], dc[3]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clipf(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clipf(DC_MPRED2(dc[8], dc[9]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clipf(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clipf(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clipf(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clipf(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clipf(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (is_luma && (flags & 0x80)) { ++ dc[0] = av_clipf(DC_MPRED2(Ldc[5], Ldc[7]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED(Ldc[5], Ldc[7], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clipf(DC_MPRED2(dc[1], dc[3]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clipf(dc[4] + tdc[5], mindc, maxdc); ++ dc[6] = av_clipf(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clipf(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clipf(DC_MPRED(Ldc[7], Ldc[13], dc[2]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clipf(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clipf(DC_MPRED(Ldc[13], Ldc[15], dc[8]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clipf(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clipf(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clipf(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clipf(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clipf(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (is_luma && (flags & 0x20)) { ++ dc[0] = av_clipf(DC_MPRED2(Tdc[10], Tdc[11]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(DC_MPRED(Tdc[10], dc[0], Tdc[11]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clipf(DC_MPRED(Tdc[11], dc[1], Tdc[14]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clipf(DC_MPRED(Tdc[14], dc[4], Tdc[15]) + tdc[5], mindc, maxdc); ++ dc[6] = av_clipf(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clipf(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clipf(DC_MPRED2(dc[2], dc[3]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clipf(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clipf(DC_MPRED2(dc[8], dc[9]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clipf(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clipf(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clipf(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clipf(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clipf(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (is_luma) { ++ dc[0] = av_clipf(DC_MPRED(LTdc[15], Ldc[5], Tdc[10]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(DC_MPRED(Tdc[10], dc[0], Tdc[11]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED(Ldc[5], Ldc[7], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clipf(DC_MPRED(Tdc[11], dc[1], Tdc[14]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clipf(DC_MPRED(Tdc[14], dc[4], Tdc[15]) + tdc[5], mindc, maxdc); ++ dc[6] = av_clipf(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clipf(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clipf(DC_MPRED(Ldc[7], Ldc[13], dc[2]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clipf(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clipf(DC_MPRED(Ldc[13], Ldc[15], dc[8]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clipf(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clipf(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clipf(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clipf(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clipf(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (!is_luma && (flags & 0x20) && (flags & 0x80)) { ++ dc[0] = av_clipf((mindc < 0 ? 0 : 1024.f) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } else if (!is_luma && (flags & 0x80)) { ++ dc[0] = av_clipf(DC_MPRED2(Ldc[1], Ldc[3]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED(Ldc[1], Ldc[3], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } else if (!is_luma && (flags & 0x20)) { ++ dc[0] = av_clipf(DC_MPRED2(Tdc[2], Tdc[3]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(DC_MPRED(Tdc[2], dc[0], Tdc[3]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } else if (!is_luma) { ++ dc[0] = av_clipf(DC_MPRED(LTdc[3], Ldc[1], Tdc[2]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clipf(DC_MPRED(Tdc[2], dc[0], Tdc[3]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clipf(DC_MPRED(Ldc[1], Ldc[3], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clipf(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } ++} ++ ++static void bink2f_decode_dc(Bink2Context *c, GetBitContext *gb, float *dc, ++ int is_luma, int q, int mindc, int maxdc, ++ int flags) ++{ ++ const int num_dc = is_luma ? 16 : 4; ++ float tdc[16] = { 0 }; ++ int dc_bits; ++ ++ dc_bits = get_bits(gb, 3); ++ if (dc_bits == 7) ++ dc_bits += get_bits(gb, 2); ++ if (!dc_bits) { ++ memset(dc, 0, sizeof(*dc) * num_dc); ++ } else { ++ for (int j = 0; j < num_dc; j += 4) { ++ for (int i = 0; i < 4; i++) ++ tdc[i + j] = get_bits(gb, dc_bits); ++ ++ for (int i = 0; i < 4; i++) ++ if (tdc[i + j] && get_bits1(gb)) ++ tdc[i + j] = -tdc[i + j]; ++ } ++ } ++ ++ if ((flags & 0x20) && (flags & 0x80) && mindc >= 0) { ++ int bits = q_dc_bits[q] + dc_bits; ++ ++ if (bits < 10) { ++ int dc_val = get_bits(gb, 10 - bits); ++ ++ if (dc_val) { ++ dc_val <<= dc_bits; ++ if (get_bits1(gb)) ++ dc_val = -dc_val; ++ } ++ tdc[0] += dc_val; ++ } ++ } ++ ++ for (int i = 0; i < num_dc; i++) ++ tdc[i] *= bink2f_dc_quant[q]; ++ ++ bink2f_predict_dc(c, is_luma, mindc, maxdc, flags, tdc); ++} ++ ++static int bink2f_decode_ac(GetBitContext *gb, const uint8_t *scan, ++ float block[4][64], unsigned cbp, ++ float q, const float qmat[64]) ++{ ++ int idx, next, val, skip; ++ VLC *val_vlc, *skip_vlc; ++ ++ for (int i = 0; i < 4; i++, cbp >>= 1) { ++ memset(block[i], 0, sizeof(**block) * 64); ++ ++ if (!(cbp & 1)) ++ continue; ++ ++ if (cbp & 0x10000) { ++ val_vlc = &bink2f_ac_val1_vlc; ++ skip_vlc = &bink2f_ac_skip1_vlc; ++ } else { ++ val_vlc = &bink2f_ac_val0_vlc; ++ skip_vlc = &bink2f_ac_skip0_vlc; ++ } ++ ++ next = 0; ++ idx = 1; ++ while (idx < 64) { ++ val = get_vlc2(gb, val_vlc->table, val_vlc->bits, 1); ++ if (val < 0) ++ return AVERROR_INVALIDDATA; ++ if (val) { ++ if (val >= 4) { ++ val -= 3; ++ val = get_bits(gb, val) + (1 << val) + 2; ++ } ++ if (get_bits1(gb)) ++ val = -val; ++ } ++ ++ block[i][scan[idx]] = val * q * qmat[scan[idx]]; ++ if (idx > 62) ++ break; ++ idx++; ++ next--; ++ if (next < 1) { ++ skip = get_vlc2(gb, skip_vlc->table, skip_vlc->bits, 1); ++ if (skip < 0) ++ return AVERROR_INVALIDDATA; ++ next = bink2_next_skips[skip]; ++ skip = bink2f_skips[skip]; ++ if (skip == 11) ++ skip = get_bits(gb, 6); ++ idx += skip; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static int bink2f_decode_intra_luma(Bink2Context *c, ++ float block[4][64], ++ unsigned *prev_cbp, int *prev_q, ++ uint8_t *dst, int stride, ++ int flags) ++{ ++ GetBitContext *gb = &c->gb; ++ float *dc = c->current_dc[c->mb_pos].dc[c->comp]; ++ int q, dq, ret; ++ unsigned cbp; ++ ++ *prev_cbp = cbp = bink2f_decode_cbp_luma(gb, *prev_cbp); ++ dq = bink2f_decode_delta_q(gb); ++ q = *prev_q + dq; ++ if (q < 0 || q >= 16) ++ return AVERROR_INVALIDDATA; ++ *prev_q = q; ++ ++ bink2f_decode_dc(c, gb, dc, 1, q, 0, 2047, flags); ++ ++ for (int i = 0; i < 4; i++) { ++ ret = bink2f_decode_ac(gb, bink2f_luma_scan, block, cbp >> (4 * i), ++ bink2f_ac_quant[q], bink2f_luma_intra_qmat); ++ if (ret < 0) ++ return ret; ++ ++ for (int j = 0; j < 4; j++) { ++ block[j][0] = dc[i * 4 + j] * 0.125f; ++ bink2f_idct_put(dst + (luma_repos[i*4+j]&3) * 8 + ++ (luma_repos[i*4+j]>>2) * 8 * stride, stride, block[j]); ++ } ++ } ++ ++ return 0; ++} ++ ++static int bink2f_decode_intra_chroma(Bink2Context *c, ++ float block[4][64], ++ unsigned *prev_cbp, int *prev_q, ++ uint8_t *dst, int stride, ++ int flags) ++{ ++ GetBitContext *gb = &c->gb; ++ float *dc = c->current_dc[c->mb_pos].dc[c->comp]; ++ int q, dq, ret; ++ unsigned cbp; ++ ++ *prev_cbp = cbp = bink2f_decode_cbp_chroma(gb, *prev_cbp); ++ dq = bink2f_decode_delta_q(gb); ++ q = *prev_q + dq; ++ if (q < 0 || q >= 16) ++ return AVERROR_INVALIDDATA; ++ *prev_q = q; ++ ++ bink2f_decode_dc(c, gb, dc, 0, q, 0, 2047, flags); ++ ++ ret = bink2f_decode_ac(gb, bink2f_chroma_scan, block, cbp, ++ bink2f_ac_quant[q], bink2f_chroma_qmat); ++ if (ret < 0) ++ return ret; ++ ++ for (int j = 0; j < 4; j++) { ++ block[j][0] = dc[j] * 0.125f; ++ bink2f_idct_put(dst + (j & 1) * 8 + (j >> 1) * 8 * stride, stride, block[j]); ++ } ++ ++ return 0; ++} ++ ++static int bink2f_decode_inter_luma(Bink2Context *c, ++ float block[4][64], ++ unsigned *prev_cbp, int *prev_q, ++ uint8_t *dst, int stride, ++ int flags) ++{ ++ GetBitContext *gb = &c->gb; ++ float *dc = c->current_dc[c->mb_pos].dc[c->comp]; ++ unsigned cbp; ++ int q, dq; ++ ++ *prev_cbp = cbp = bink2f_decode_cbp_luma(gb, *prev_cbp); ++ dq = bink2f_decode_delta_q(gb); ++ q = *prev_q + dq; ++ if (q < 0 || q >= 16) ++ return AVERROR_INVALIDDATA; ++ *prev_q = q; ++ ++ bink2f_decode_dc(c, gb, dc, 1, q, -1023, 1023, 0xA8); ++ ++ for (int i = 0; i < 4; i++) { ++ bink2f_decode_ac(gb, bink2f_luma_scan, block, cbp >> (i * 4), ++ bink2f_ac_quant[q], bink2f_luma_inter_qmat); ++ for (int j = 0; j < 4; j++) { ++ block[j][0] = dc[i * 4 + j] * 0.125f; ++ bink2f_idct_add(dst + (luma_repos[i*4+j]&3) * 8 + ++ (luma_repos[i*4+j]>>2) * 8 * stride, stride, ++ block[j]); ++ } ++ } ++ ++ return 0; ++} ++ ++static int bink2f_decode_inter_chroma(Bink2Context *c, ++ float block[4][64], ++ unsigned *prev_cbp, int *prev_q, ++ uint8_t *dst, int stride, ++ int flags) ++{ ++ GetBitContext *gb = &c->gb; ++ float *dc = c->current_dc[c->mb_pos].dc[c->comp]; ++ unsigned cbp; ++ int q, dq; ++ ++ *prev_cbp = cbp = bink2f_decode_cbp_chroma(gb, *prev_cbp); ++ dq = bink2f_decode_delta_q(gb); ++ q = *prev_q + dq; ++ if (q < 0 || q >= 16) ++ return AVERROR_INVALIDDATA; ++ *prev_q = q; ++ ++ bink2f_decode_dc(c, gb, dc, 0, q, -1023, 1023, 0xA8); ++ ++ bink2f_decode_ac(gb, bink2f_chroma_scan, block, cbp, ++ bink2f_ac_quant[q], bink2f_chroma_qmat); ++ ++ for (int i = 0; i < 4; i++) { ++ block[i][0] = dc[i] * 0.125f; ++ bink2f_idct_add(dst + (i & 1) * 8 + (i >> 1) * 8 * stride, stride, ++ block[i]); ++ } ++ ++ return 0; ++} ++ ++static void bink2f_predict_mv(Bink2Context *c, int x, int y, int flags, MVectors mv) ++{ ++ MVectors *c_mv = &c->current_mv[c->mb_pos].mv; ++ MVectors *l_mv = &c->current_mv[FFMAX(c->mb_pos - 1, 0)].mv; ++ MVectors *lt_mv = &c->prev_mv[FFMAX(c->mb_pos - 1, 0)].mv; ++ MVectors *t_mv = &c->prev_mv[c->mb_pos].mv; ++ ++ if (!(flags & 0x80)) { ++ if (flags & 0x20) { ++ c_mv->v[0][0] = mv.v[0][0] + mid_pred(t_mv->v[0][0], t_mv->v[2][0], t_mv->v[3][0]); ++ c_mv->v[0][1] = mv.v[0][1] + mid_pred(t_mv->v[0][1], t_mv->v[2][1], t_mv->v[3][1]); ++ c_mv->v[1][0] = mv.v[1][0] + mid_pred(t_mv->v[2][0], t_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[1][1] = mv.v[1][1] + mid_pred(t_mv->v[2][1], t_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[2][0] = mv.v[2][0] + mid_pred(t_mv->v[2][0], c_mv->v[0][0], c_mv->v[1][0]); ++ c_mv->v[2][1] = mv.v[2][1] + mid_pred(t_mv->v[2][1], c_mv->v[0][1], c_mv->v[1][1]); ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred(c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred(c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } else { ++ c_mv->v[0][0] = mv.v[0][0] + mid_pred(lt_mv->v[3][0], t_mv->v[2][0], l_mv->v[1][0]); ++ c_mv->v[0][1] = mv.v[0][1] + mid_pred(lt_mv->v[3][1], t_mv->v[2][1], l_mv->v[1][1]); ++ c_mv->v[1][0] = mv.v[1][0] + mid_pred( t_mv->v[2][0], t_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[1][1] = mv.v[1][1] + mid_pred( t_mv->v[2][1], t_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[2][0] = mv.v[2][0] + mid_pred( t_mv->v[2][0], c_mv->v[0][0], c_mv->v[1][0]); ++ c_mv->v[2][1] = mv.v[2][1] + mid_pred( t_mv->v[2][1], c_mv->v[0][1], c_mv->v[1][1]); ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred( c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred( c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } ++ } else { ++ if (flags & 0x20) { ++ c_mv->v[0][0] = mv.v[0][0]; ++ c_mv->v[0][1] = mv.v[0][1]; ++ c_mv->v[1][0] = mv.v[1][0]; ++ c_mv->v[1][1] = mv.v[1][1]; ++ c_mv->v[2][0] = mv.v[2][0]; ++ c_mv->v[2][1] = mv.v[2][1]; ++ c_mv->v[3][0] = mv.v[3][0]; ++ c_mv->v[3][1] = mv.v[3][1]; ++ } else { ++ c_mv->v[0][0] = mv.v[0][0] + mid_pred(l_mv->v[0][0], l_mv->v[1][0], l_mv->v[3][0]); ++ c_mv->v[0][1] = mv.v[0][1] + mid_pred(l_mv->v[0][1], l_mv->v[1][1], l_mv->v[3][1]); ++ c_mv->v[2][0] = mv.v[2][0] + mid_pred(l_mv->v[1][0], l_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[2][1] = mv.v[2][1] + mid_pred(l_mv->v[1][1], l_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[1][0] = mv.v[1][0] + mid_pred(l_mv->v[1][0], c_mv->v[0][0], c_mv->v[2][0]); ++ c_mv->v[1][1] = mv.v[1][1] + mid_pred(l_mv->v[1][1], c_mv->v[0][1], c_mv->v[2][1]); ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred(c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred(c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } ++ } ++} ++ ++#define CH1FILTER(src) ((6*(src)[0] + 2*(src)[1] + 4) >> 3) ++#define CH2FILTER(src) (( (src)[0] + (src)[1] + 1) >> 1) ++#define CH3FILTER(src) ((2*(src)[0] + 6*(src)[1] + 4) >> 3) ++ ++#define CV1FILTER(src, i) ((6*(src)[0] + 2*(src)[i] + 4) >> 3) ++#define CV2FILTER(src, i) (( (src)[0] + (src)[i] + 1) >> 1) ++#define CV3FILTER(src, i) ((2*(src)[0] + 6*(src)[i] + 4) >> 3) ++ ++static void bink2f_c_mc(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height, ++ int mv_x, int mv_y, ++ int mode) ++{ ++ uint8_t *msrc; ++ uint8_t temp[8*9]; ++ ++ if (mv_x < 0 || mv_x >= width || ++ mv_y < 0 || mv_y >= height) ++ return; ++ ++ msrc = src + mv_x + mv_y * sstride; ++ ++ switch (mode) { ++ case 0: ++ copy_block8(dst, msrc, stride, sstride, 8); ++ break; ++ case 1: ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CH1FILTER(msrc + i)); ++ dst += stride; ++ msrc += sstride; ++ } ++ break; ++ case 2: ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CH2FILTER(msrc + i)); ++ dst += stride; ++ msrc += sstride; ++ } ++ break; ++ case 3: ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CH3FILTER(msrc + i)); ++ dst += stride; ++ msrc += sstride; ++ } ++ break; ++ case 4: ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i*stride] = av_clip_uint8(CV1FILTER(msrc + i*sstride, sstride)); ++ dst += 1; ++ msrc += 1; ++ } ++ break; ++ case 5: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH1FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV1FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 6: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH2FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV1FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 7: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH3FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV1FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 8: ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i*stride] = av_clip_uint8(CV2FILTER(msrc + i*sstride, sstride)); ++ dst += 1; ++ msrc += 1; ++ } ++ break; ++ case 9: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH1FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV2FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 10: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH2FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV2FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 11: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH3FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV2FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 12: ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i*stride] = av_clip_uint8(CV3FILTER(msrc + i*sstride, sstride)); ++ dst += 1; ++ msrc += 1; ++ } ++ break; ++ case 13: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH1FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV3FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 14: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH2FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV3FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ case 15: ++ for (int i = 0; i < 9; i++) { ++ for (int j = 0; j < 8; j++) ++ temp[i*8+j] = av_clip_uint8(CH3FILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 8; j++) { ++ for (int i = 0; i < 8; i++) ++ dst[i] = av_clip_uint8(CV3FILTER(temp+j*8+i, 8)); ++ dst += stride; ++ } ++ break; ++ } ++} ++ ++static void bink2f_y_mc(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height, ++ int mv_x, int mv_y, int mode) ++{ ++ uint8_t *msrc; ++ ++ if (mv_x < 0 || mv_x >= width || ++ mv_y < 0 || mv_y >= height) ++ return; ++ ++ msrc = src + mv_x + mv_y * sstride; ++ ++ if (mode == 0) { ++ copy_block16(dst, msrc, stride, sstride, 16); ++ } else if (mode == 1) { ++ for (int j = 0; j < 16; j++) { ++ for (int i = 0; i < 16; i++) ++ dst[i] = av_clip_uint8(LHFILTER(msrc + i)); ++ dst += stride; ++ msrc += sstride; ++ } ++ } else if (mode == 2) { ++ for (int j = 0; j < 16; j++) { ++ for (int i = 0; i < 16; i++) ++ dst[i*stride] = av_clip_uint8(LVFILTER(msrc + i*sstride, sstride)); ++ dst += 1; ++ msrc += 1; ++ } ++ } else if (mode == 3) { ++ uint8_t temp[21 * 16]; ++ ++ msrc -= 2 * sstride; ++ for (int i = 0; i < 21; i++) { ++ for (int j = 0; j < 16; j++) ++ temp[i*16+j] = av_clip_uint8(LHFILTER(msrc + j)); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 16; j++) { ++ for (int i = 0; i < 16; i++) ++ dst[i] = av_clip_uint8(LVFILTER(temp+(j+2)*16+i, 16)); ++ dst += stride; ++ } ++ } ++} ++ ++static int bink2f_mcompensate_chroma(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height) ++{ ++ MVectors *mv = &c->current_mv[c->mb_pos].mv; ++ int mv_x, mv_y, mode; ++ ++ mv_x = (mv->v[0][0] >> 2) + x; ++ mv_y = (mv->v[0][1] >> 2) + y; ++ mode = mv->v[0][0] & 3; ++ mode |= (mv->v[0][1] & 3) << 2; ++ bink2f_c_mc(c, x, y, dst + x, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[1][0] >> 2) + x + 8; ++ mv_y = (mv->v[1][1] >> 2) + y; ++ mode = mv->v[1][0] & 3; ++ mode |= (mv->v[1][1] & 3) << 2; ++ bink2f_c_mc(c, x, y, dst + x + 8, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[2][0] >> 2) + x; ++ mv_y = (mv->v[2][1] >> 2) + y + 8; ++ mode = mv->v[2][0] & 3; ++ mode |= (mv->v[2][1] & 3) << 2; ++ bink2f_c_mc(c, x, y, dst + x + 8 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[3][0] >> 2) + x + 8; ++ mv_y = (mv->v[3][1] >> 2) + y + 8; ++ mode = mv->v[3][0] & 3; ++ mode |= (mv->v[3][1] & 3) << 2; ++ bink2f_c_mc(c, x, y, dst + x + 8 + 8 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ return 0; ++} ++ ++static float bink2f_average_block(uint8_t *src, int stride) ++{ ++ int sum = 0; ++ ++ for (int i = 0; i < 8; i++) { ++ int avg_a = (src[i+0*stride] + src[i+1*stride] + 1) >> 1; ++ int avg_b = (src[i+2*stride] + src[i+3*stride] + 1) >> 1; ++ int avg_c = (src[i+4*stride] + src[i+5*stride] + 1) >> 1; ++ int avg_d = (src[i+6*stride] + src[i+7*stride] + 1) >> 1; ++ int avg_e = (avg_a + avg_b + 1) >> 1; ++ int avg_f = (avg_c + avg_d + 1) >> 1; ++ int avg_g = (avg_e + avg_f + 1) >> 1; ++ sum += avg_g; ++ } ++ ++ return sum; ++} ++ ++static void bink2f_average_chroma(Bink2Context *c, int x, int y, ++ uint8_t *src, int stride, ++ float *dc) ++{ ++ for (int i = 0; i < 4; i++) { ++ int X = i & 1; ++ int Y = i >> 1; ++ dc[i] = bink2f_average_block(src + x + X * 8 + (y + Y * 8) * stride, stride); ++ } ++} ++ ++static void bink2f_average_luma(Bink2Context *c, int x, int y, ++ uint8_t *src, int stride, ++ float *dc) ++{ ++ for (int i = 0; i < 16; i++) { ++ int I = luma_repos[i]; ++ int X = I & 3; ++ int Y = I >> 2; ++ dc[i] = bink2f_average_block(src + x + X * 8 + (y + Y * 8) * stride, stride); ++ } ++} ++ ++static int bink2f_mcompensate_luma(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height) ++{ ++ MVectors *mv = &c->current_mv[c->mb_pos].mv; ++ int mv_x, mv_y, mode; ++ ++ mv_x = (mv->v[0][0] >> 1) + x; ++ mv_y = (mv->v[0][1] >> 1) + y; ++ mode = mv->v[0][0] & 1; ++ mode |= (mv->v[0][1] & 1) << 1; ++ bink2f_y_mc(c, x, y, dst + x, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[1][0] >> 1) + x + 16; ++ mv_y = (mv->v[1][1] >> 1) + y; ++ mode = mv->v[1][0] & 1; ++ mode |= (mv->v[1][1] & 1) << 1; ++ bink2f_y_mc(c, x, y, dst + x + 16, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[2][0] >> 1) + x; ++ mv_y = (mv->v[2][1] >> 1) + y + 16; ++ mode = mv->v[2][0] & 1; ++ mode |= (mv->v[2][1] & 1) << 1; ++ bink2f_y_mc(c, x, y, dst + x + 16 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[3][0] >> 1) + x + 16; ++ mv_y = (mv->v[3][1] >> 1) + y + 16; ++ mode = mv->v[3][0] & 1; ++ mode |= (mv->v[3][1] & 1) << 1; ++ bink2f_y_mc(c, x, y, dst + x + 16 + 16 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ return 0; ++} ++ ++static int bink2f_decode_mv(Bink2Context *c, GetBitContext *gb, int x, int y, ++ int flags, MVectors *mv) ++{ ++ for (int i = 0; i < 2; i++) { ++ int val = 0, bits = get_bits(gb, 3); ++ ++ if (bits == 7) ++ bits += get_bits(gb, 2); ++ if (bits) { ++ for (int j = 0; j < 4; j++) ++ mv->v[j][i] = get_bits(gb, bits); ++ for (int j = 0; j < 4; j++) ++ if (mv->v[j][i] && get_bits1(gb)) ++ mv->v[j][i] = -mv->v[j][i]; ++ } ++ ++ if ((flags & 0x80) && (flags & 0x20)) { ++ val = get_bits(gb, 5) * 16; ++ if (val && get_bits1(gb)) ++ val = -val; ++ } ++ ++ mv->v[0][i] += val; ++ mv->v[1][i] += val; ++ mv->v[2][i] += val; ++ mv->v[3][i] += val; ++ } ++ ++ return 0; ++} ++ ++static int bink2f_decode_slice(Bink2Context *c, ++ uint8_t *dst[4], int stride[4], ++ uint8_t *src[4], int sstride[4], ++ int is_kf, int start, int end) ++{ ++ GetBitContext *gb = &c->gb; ++ int w = c->avctx->width; ++ int h = c->avctx->height; ++ int flags, ret = 0; ++ ++ memset(c->prev_mv, 0, ((c->avctx->width + 31) / 32) * sizeof(*c->prev_mv)); ++ ++ for (int y = start; y < end; y += 32) { ++ unsigned y_cbp_intra = 0, u_cbp_intra = 0, v_cbp_intra = 0, a_cbp_intra = 0; ++ unsigned y_cbp_inter = 0, u_cbp_inter = 0, v_cbp_inter = 0, a_cbp_inter = 0; ++ int y_intra_q = 8, u_intra_q = 8, v_intra_q = 8, a_intra_q = 8; ++ int y_inter_q = 8, u_inter_q = 8, v_inter_q = 8, a_inter_q = 8; ++ ++ memset(c->current_mv, 0, ((c->avctx->width + 31) / 32) * sizeof(*c->current_mv)); ++ ++ for (int x = 0; x < c->avctx->width; x += 32) { ++ MVectors mv = { 0 }; ++ int type = is_kf ? INTRA_BLOCK : get_bits(gb, 2); ++ ++ c->mb_pos = x / 32; ++ c->current_dc[c->mb_pos].block_type = type; ++ flags = 0; ++ if (y == start) ++ flags |= 0x80; ++ if (!x) ++ flags |= 0x20; ++ if (x == 32) ++ flags |= 0x200; ++ if (x + 32 >= c->avctx->width) ++ flags |= 0x40; ++ ++ switch (type) { ++ case INTRA_BLOCK: ++ if (!(flags & 0xA0) && c->prev_dc[c->mb_pos - 1].block_type != INTRA_BLOCK) { ++ bink2f_average_luma (c, x -32, -32, dst[0], stride[0], c->prev_dc[c->mb_pos - 1].dc[0]); ++ bink2f_average_chroma(c, x/2-16, -16, dst[2], stride[2], c->prev_dc[c->mb_pos - 1].dc[1]); ++ bink2f_average_chroma(c, x/2-16, -16, dst[1], stride[1], c->prev_dc[c->mb_pos - 1].dc[2]); ++ } ++ if (!(flags & 0x20) && c->current_dc[c->mb_pos - 1].block_type != INTRA_BLOCK) { ++ bink2f_average_luma (c, x -32, 0, dst[0], stride[0], c->current_dc[c->mb_pos - 1].dc[0]); ++ bink2f_average_chroma(c, x/2-16, 0, dst[2], stride[2], c->current_dc[c->mb_pos - 1].dc[1]); ++ bink2f_average_chroma(c, x/2-16, 0, dst[1], stride[1], c->current_dc[c->mb_pos - 1].dc[2]); ++ } ++ if ((flags & 0x20) && !(flags & 0x80) && c->prev_dc[c->mb_pos + 1].block_type != INTRA_BLOCK) { ++ bink2f_average_luma (c, x +32, -32, dst[0], stride[0], c->prev_dc[c->mb_pos + 1].dc[0]); ++ bink2f_average_chroma(c, x/2+16, -16, dst[2], stride[2], c->prev_dc[c->mb_pos + 1].dc[1]); ++ bink2f_average_chroma(c, x/2+16, -16, dst[1], stride[1], c->prev_dc[c->mb_pos + 1].dc[2]); ++ } ++ if (!(flags & 0x80) && c->prev_dc[c->mb_pos].block_type != INTRA_BLOCK) { ++ bink2f_average_luma (c, x, -32, dst[0], stride[0], c->prev_dc[c->mb_pos].dc[0]); ++ bink2f_average_chroma(c, x/2, -16, dst[2], stride[2], c->prev_dc[c->mb_pos].dc[1]); ++ bink2f_average_chroma(c, x/2, -16, dst[1], stride[1], c->prev_dc[c->mb_pos].dc[2]); ++ } ++ ++ bink2f_predict_mv(c, x, y, flags, mv); ++ c->comp = 0; ++ ret = bink2f_decode_intra_luma(c, c->block, &y_cbp_intra, &y_intra_q, ++ dst[0] + x, stride[0], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 1; ++ ret = bink2f_decode_intra_chroma(c, c->block, &u_cbp_intra, &u_intra_q, ++ dst[2] + x/2, stride[2], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2f_decode_intra_chroma(c, c->block, &v_cbp_intra, &v_intra_q, ++ dst[1] + x/2, stride[1], flags); ++ if (ret < 0) ++ goto fail; ++ if (c->has_alpha) { ++ c->comp = 3; ++ ret = bink2f_decode_intra_luma(c, c->block, &a_cbp_intra, &a_intra_q, ++ dst[3] + x, stride[3], flags); ++ if (ret < 0) ++ goto fail; ++ } ++ break; ++ case SKIP_BLOCK: ++ copy_block16(dst[0] + x, src[0] + x + sstride[0] * y, ++ stride[0], sstride[0], 32); ++ copy_block16(dst[0] + x + 16, src[0] + x + 16 + sstride[0] * y, ++ stride[0], sstride[0], 32); ++ copy_block16(dst[1] + (x/2), src[1] + (x/2) + sstride[1] * (y/2), ++ stride[1], sstride[1], 16); ++ copy_block16(dst[2] + (x/2), src[2] + (x/2) + sstride[2] * (y/2), ++ stride[2], sstride[2], 16); ++ if (c->has_alpha) { ++ copy_block16(dst[3] + x, src[3] + x + sstride[3] * y, ++ stride[3], sstride[3], 32); ++ copy_block16(dst[3] + x + 16, src[3] + x + 16 + sstride[3] * y, ++ stride[3], sstride[3], 32); ++ } ++ break; ++ case MOTION_BLOCK: ++ bink2f_decode_mv(c, gb, x, y, flags, &mv); ++ bink2f_predict_mv(c, x, y, flags, mv); ++ c->comp = 0; ++ ret = bink2f_mcompensate_luma(c, x, y, ++ dst[0], stride[0], ++ src[0], sstride[0], ++ w, h); ++ if (ret < 0) ++ goto fail; ++ c->comp = 1; ++ ret = bink2f_mcompensate_chroma(c, x/2, y/2, ++ dst[2], stride[2], ++ src[2], sstride[2], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2f_mcompensate_chroma(c, x/2, y/2, ++ dst[1], stride[1], ++ src[1], sstride[1], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ break; ++ case RESIDUE_BLOCK: ++ bink2f_decode_mv(c, gb, x, y, flags, &mv); ++ bink2f_predict_mv(c, x, y, flags, mv); ++ ret = bink2f_mcompensate_luma(c, x, y, ++ dst[0], stride[0], ++ src[0], sstride[0], ++ w, h); ++ if (ret < 0) ++ goto fail; ++ ret = bink2f_mcompensate_chroma(c, x/2, y/2, ++ dst[2], stride[2], ++ src[2], sstride[2], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ ret = bink2f_mcompensate_chroma(c, x/2, y/2, ++ dst[1], stride[1], ++ src[1], sstride[1], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ c->comp = 0; ++ ret = bink2f_decode_inter_luma(c, c->block, &y_cbp_inter, &y_inter_q, ++ dst[0] + x, stride[0], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 1; ++ ret = bink2f_decode_inter_chroma(c, c->block, &u_cbp_inter, &u_inter_q, ++ dst[2] + x/2, stride[2], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2f_decode_inter_chroma(c, c->block, &v_cbp_inter, &v_inter_q, ++ dst[1] + x/2, stride[1], flags); ++ if (ret < 0) ++ goto fail; ++ if (c->has_alpha) { ++ c->comp = 3; ++ ret = bink2f_decode_inter_luma(c, c->block, &a_cbp_inter, &a_inter_q, ++ dst[3] + x, stride[3], flags); ++ if (ret < 0) ++ goto fail; ++ } ++ break; ++ default: ++ return AVERROR_INVALIDDATA; ++ } ++ } ++ ++ dst[0] += stride[0] * 32; ++ dst[1] += stride[1] * 16; ++ dst[2] += stride[2] * 16; ++ if (c->has_alpha) ++ dst[3] += stride[3] * 32; ++ ++ FFSWAP(MVPredict *, c->current_mv, c->prev_mv); ++ FFSWAP(DCPredict *, c->current_dc, c->prev_dc); ++ } ++fail: ++ emms_c(); ++ ++ return ret; ++} ++ ++#endif /* AVCODEC_BINK2F_H */ +diff --git a/libavcodec/bink2g.h b/libavcodec/bink2g.h +new file mode 100644 +index 0000000000..be50146e4f +--- /dev/null ++++ b/libavcodec/bink2g.h +@@ -0,0 +1,1870 @@ ++/* ++ * Bink video 2 decoder ++ * Copyright (c) 2014 Konstantin Shishkov ++ * Copyright (c) 2019 Paul B Mahol ++ * ++ * This file is part of FFmpeg. ++ * ++ * FFmpeg is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * FFmpeg is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with FFmpeg; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#ifndef AVCODEC_BINK2G_H ++#define AVCODEC_BINK2G_H ++ ++#include ++#include "avcodec.h" ++#include "get_bits.h" ++#include "bink2.h" ++ ++static const uint8_t bink2g_scan[64] = { ++ 0, 8, 1, 2, 9, 16, 24, 17, ++ 10, 3, 4, 11, 18, 25, 32, 40, ++ 33, 26, 19, 12, 5, 6, 13, 20, ++ 27, 34, 41, 48, 56, 49, 42, 35, ++ 28, 21, 14, 7, 15, 22, 29, 36, ++ 43, 50, 57, 58, 51, 44, 37, 30, ++ 23, 31, 38, 45, 52, 59, 60, 53, ++ 46, 39, 47, 54, 61, 62, 55, 63, ++}; ++ ++static const uint16_t bink2g_ac_skip_codes[2][NUM_AC_SKIPS] = { ++ { ++ 0x01, 0x00, 0x004, 0x02C, 0x06C, 0x0C, 0x4C, ++ 0xAC, 0xEC, 0x12C, 0x16C, 0x1AC, 0x02, 0x1C, ++ }, ++ { ++ 0x01, 0x04, 0x00, 0x08, 0x02, 0x32, 0x0A, ++ 0x12, 0x3A, 0x7A, 0xFA, 0x72, 0x06, 0x1A, ++ }, ++}; ++ ++static const uint8_t bink2g_ac_skip_bits[2][NUM_AC_SKIPS] = { ++ { 1, 3, 4, 9, 9, 7, 7, 9, 8, 9, 9, 9, 2, 5 }, ++ { 1, 3, 4, 4, 5, 7, 5, 6, 7, 8, 8, 7, 3, 6 }, ++}; ++ ++static const uint8_t bink2g_mv_codes[] = { ++ 0x01, 0x06, 0x0C, 0x1C, 0x18, 0x38, 0x58, 0x78, ++ 0x68, 0x48, 0x28, 0x08, 0x14, 0x04, 0x02, 0x00, ++}; ++ ++static const uint8_t bink2g_mv_bits[] = { ++ 1, 3, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 5, 5, 3, 4, ++}; ++ ++static const uint8_t bink2g_skips[] = { ++ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 64, 0, 0, 0, ++}; ++ ++static uint8_t bink2g_chroma_cbp_pat[16] = { ++ 0x00, 0x00, 0x00, 0x0F, ++ 0x00, 0x0F, 0x0F, 0x0F, ++ 0x00, 0x0F, 0x0F, 0x0F, ++ 0x0F, 0x0F, 0x0F, 0x0F, ++}; ++ ++static const int32_t bink2g_dc_pat[] = { ++ 1024, 1218, 1448, 1722, 2048, ++ 2435, 2896, 3444, 4096, 4871, ++ 5793, 6889, 8192, 9742, 11585, 13777, 16384, ++ 19484, 23170, 27555, 32768, 38968, 46341, ++ 55109, 65536, 77936, 92682, 110218, 131072, ++ 155872, 185364, 220436, 262144, 311744, ++ 370728, 440872, 524288, ++}; ++ ++static const uint16_t bink2g_luma_intra_qmat[4][64] = { ++ { ++ 1024, 1432, 1506, 1181, ++ 1843, 2025, 5271, 8592, ++ 1313, 1669, 1630, 1672, ++ 2625, 3442, 8023, 12794, ++ 1076, 1755, 1808, 1950, ++ 3980, 4875, 8813, 11909, ++ 1350, 1868, 2127, 2016, ++ 4725, 4450, 7712, 9637, ++ 2458, 3103, 4303, 4303, ++ 6963, 6835, 11079, 13365, ++ 3375, 5704, 5052, 6049, ++ 9198, 7232, 10725, 9834, ++ 5486, 7521, 7797, 7091, ++ 11079, 10016, 13559, 12912, ++ 7279, 7649, 7020, 6097, ++ 9189, 9047, 12661, 13768, ++ }, ++ { ++ 1218, 1703, 1791, 1405, ++ 2192, 2408, 6268, 10218, ++ 1561, 1985, 1938, 1988, ++ 3122, 4093, 9541, 15215, ++ 1279, 2087, 2150, 2319, ++ 4733, 5798, 10481, 14162, ++ 1606, 2222, 2530, 2398, ++ 5619, 5292, 9171, 11460, ++ 2923, 3690, 5117, 5118, ++ 8281, 8128, 13176, 15894, ++ 4014, 6783, 6008, 7194, ++ 10938, 8600, 12755, 11694, ++ 6524, 8944, 9272, 8433, ++ 13176, 11911, 16125, 15354, ++ 8657, 9096, 8348, 7250, ++ 10927, 10759, 15056, 16373, ++ }, ++ { ++ 1448, 2025, 2130, 1671, ++ 2607, 2864, 7454, 12151, ++ 1856, 2360, 2305, 2364, ++ 3713, 4867, 11346, 18094, ++ 1521, 2482, 2557, 2758, ++ 5628, 6894, 12464, 16841, ++ 1909, 2642, 3008, 2852, ++ 6683, 6293, 10906, 13629, ++ 3476, 4388, 6085, 6086, ++ 9847, 9666, 15668, 18901, ++ 4773, 8066, 7145, 8555, ++ 13007, 10227, 15168, 13907, ++ 7758, 10637, 11026, 10028, ++ 15668, 14165, 19175, 18259, ++ 10294, 10817, 9927, 8622, ++ 12995, 12794, 17905, 19470, ++ }, ++ { ++ 1722, 2408, 2533, 1987, ++ 3100, 3406, 8864, 14450, ++ 2208, 2807, 2741, 2811, ++ 4415, 5788, 13493, 21517, ++ 1809, 2951, 3041, 3280, ++ 6693, 8199, 14822, 20028, ++ 2271, 3142, 3578, 3391, ++ 7947, 7484, 12969, 16207, ++ 4133, 5218, 7236, 7238, ++ 11711, 11495, 18633, 22478, ++ 5677, 9592, 8497, 10174, ++ 15469, 12162, 18038, 16538, ++ 9226, 12649, 13112, 11926, ++ 18633, 16845, 22804, 21715, ++ 12242, 12864, 11806, 10254, ++ 15454, 15215, 21293, 23155, ++ }, ++}; ++ ++static const uint16_t bink2g_chroma_intra_qmat[4][64] = { ++ { ++ 1024, 1193, 1434, 2203, ++ 5632, 4641, 5916, 6563, ++ 1193, 1622, 1811, 3606, ++ 6563, 5408, 6894, 7649, ++ 1434, 1811, 3515, 4875, ++ 5916, 4875, 6215, 6894, ++ 2203, 3606, 4875, 3824, ++ 4641, 3824, 4875, 5408, ++ 5632, 6563, 5916, 4641, ++ 5632, 4641, 5916, 6563, ++ 4641, 5408, 4875, 3824, ++ 4641, 3824, 4875, 5408, ++ 5916, 6894, 6215, 4875, ++ 5916, 4875, 6215, 6894, ++ 6563, 7649, 6894, 5408, ++ 6563, 5408, 6894, 7649, ++ }, ++ { ++ 1218, 1419, 1706, 2620, ++ 6698, 5519, 7035, 7805, ++ 1419, 1929, 2153, 4288, ++ 7805, 6432, 8199, 9096, ++ 1706, 2153, 4180, 5798, ++ 7035, 5798, 7390, 8199, ++ 2620, 4288, 5798, 4548, ++ 5519, 4548, 5798, 6432, ++ 6698, 7805, 7035, 5519, ++ 6698, 5519, 7035, 7805, ++ 5519, 6432, 5798, 4548, ++ 5519, 4548, 5798, 6432, ++ 7035, 8199, 7390, 5798, ++ 7035, 5798, 7390, 8199, ++ 7805, 9096, 8199, 6432, ++ 7805, 6432, 8199, 9096, ++ }, ++ { ++ 1448, 1688, 2028, 3116, ++ 7965, 6563, 8367, 9282, ++ 1688, 2294, 2561, 5099, ++ 9282, 7649, 9750, 10817, ++ 2028, 2561, 4971, 6894, ++ 8367, 6894, 8789, 9750, ++ 3116, 5099, 6894, 5408, ++ 6563, 5408, 6894, 7649, ++ 7965, 9282, 8367, 6563, ++ 7965, 6563, 8367, 9282, ++ 6563, 7649, 6894, 5408, ++ 6563, 5408, 6894, 7649, ++ 8367, 9750, 8789, 6894, ++ 8367, 6894, 8789, 9750, ++ 9282, 10817, 9750, 7649, ++ 9282, 7649, 9750, 10817, ++ }, ++ { ++ 1722, 2007, 2412, 3706, ++ 9472, 7805, 9950, 11038, ++ 2007, 2729, 3045, 6064, ++ 11038, 9096, 11595, 12864, ++ 2412, 3045, 5912, 8199, ++ 9950, 8199, 10452, 11595, ++ 3706, 6064, 8199, 6432, ++ 7805, 6432, 8199, 9096, ++ 9472, 11038, 9950, 7805, ++ 9472, 7805, 9950, 11038, ++ 7805, 9096, 8199, 6432, ++ 7805, 6432, 8199, 9096, ++ 9950, 11595, 10452, 8199, ++ 9950, 8199, 10452, 11595, ++ 11038, 12864, 11595, 9096, ++ 11038, 9096, 11595, 12864, ++ }, ++}; ++ ++static const uint16_t bink2g_inter_qmat[4][64] = { ++ { ++ 1024, 1193, 1076, 844, ++ 1052, 914, 1225, 1492, ++ 1193, 1391, 1254, 983, ++ 1227, 1065, 1463, 1816, ++ 1076, 1254, 1161, 936, ++ 1195, 1034, 1444, 1741, ++ 844, 983, 936, 811, ++ 1055, 927, 1305, 1584, ++ 1052, 1227, 1195, 1055, ++ 1451, 1336, 1912, 2354, ++ 914, 1065, 1034, 927, ++ 1336, 1313, 1945, 2486, ++ 1225, 1463, 1444, 1305, ++ 1912, 1945, 3044, 4039, ++ 1492, 1816, 1741, 1584, ++ 2354, 2486, 4039, 5679, ++ }, ++ { ++ 1218, 1419, 1279, 1003, ++ 1252, 1087, 1457, 1774, ++ 1419, 1654, 1491, 1169, ++ 1459, 1267, 1739, 2159, ++ 1279, 1491, 1381, 1113, ++ 1421, 1230, 1717, 2070, ++ 1003, 1169, 1113, 965, ++ 1254, 1103, 1552, 1884, ++ 1252, 1459, 1421, 1254, ++ 1725, 1589, 2274, 2799, ++ 1087, 1267, 1230, 1103, ++ 1589, 1562, 2313, 2956, ++ 1457, 1739, 1717, 1552, ++ 2274, 2313, 3620, 4803, ++ 1774, 2159, 2070, 1884, ++ 2799, 2956, 4803, 6753, ++ }, ++ { ++ 1448, 1688, 1521, 1193, ++ 1488, 1293, 1732, 2110, ++ 1688, 1967, 1773, 1391, ++ 1735, 1507, 2068, 2568, ++ 1521, 1773, 1642, 1323, ++ 1690, 1462, 2042, 2462, ++ 1193, 1391, 1323, 1147, ++ 1492, 1311, 1845, 2241, ++ 1488, 1735, 1690, 1492, ++ 2052, 1889, 2704, 3328, ++ 1293, 1507, 1462, 1311, ++ 1889, 1857, 2751, 3515, ++ 1732, 2068, 2042, 1845, ++ 2704, 2751, 4306, 5712, ++ 2110, 2568, 2462, 2241, ++ 3328, 3515, 5712, 8031, ++ }, ++ { ++ 1722, 2007, 1809, 1419, ++ 1770, 1537, 2060, 2509, ++ 2007, 2339, 2108, 1654, ++ 2063, 1792, 2460, 3054, ++ 1809, 2108, 1953, 1574, ++ 2010, 1739, 2428, 2928, ++ 1419, 1654, 1574, 1364, ++ 1774, 1559, 2195, 2664, ++ 1770, 2063, 2010, 1774, ++ 2440, 2247, 3216, 3958, ++ 1537, 1792, 1739, 1559, ++ 2247, 2209, 3271, 4181, ++ 2060, 2460, 2428, 2195, ++ 3216, 3271, 5120, 6793, ++ 2509, 3054, 2928, 2664, ++ 3958, 4181, 6793, 9550, ++ }, ++}; ++ ++static inline void bink2g_idct_1d(int16_t *blk, int step, int shift) ++{ ++#define idct_mul_a(val) ((val) + ((val) >> 2)) ++#define idct_mul_b(val) ((val) >> 1) ++#define idct_mul_c(val) ((val) - ((val) >> 2) - ((val) >> 4)) ++#define idct_mul_d(val) ((val) + ((val) >> 2) - ((val) >> 4)) ++#define idct_mul_e(val) ((val) >> 2) ++ int tmp00 = blk[3*step] + blk[5*step]; ++ int tmp01 = blk[3*step] - blk[5*step]; ++ int tmp02 = idct_mul_a(blk[2*step]) + idct_mul_b(blk[6*step]); ++ int tmp03 = idct_mul_b(blk[2*step]) - idct_mul_a(blk[6*step]); ++ int tmp0 = (blk[0*step] + blk[4*step]) + tmp02; ++ int tmp1 = (blk[0*step] + blk[4*step]) - tmp02; ++ int tmp2 = blk[0*step] - blk[4*step]; ++ int tmp3 = blk[1*step] + tmp00; ++ int tmp4 = blk[1*step] - tmp00; ++ int tmp5 = tmp01 + blk[7*step]; ++ int tmp6 = tmp01 - blk[7*step]; ++ int tmp7 = tmp4 + idct_mul_c(tmp6); ++ int tmp8 = idct_mul_c(tmp4) - tmp6; ++ int tmp9 = idct_mul_d(tmp3) + idct_mul_e(tmp5); ++ int tmp10 = idct_mul_e(tmp3) - idct_mul_d(tmp5); ++ int tmp11 = tmp2 + tmp03; ++ int tmp12 = tmp2 - tmp03; ++ ++ blk[0*step] = (tmp0 + tmp9) >> shift; ++ blk[1*step] = (tmp11 + tmp7) >> shift; ++ blk[2*step] = (tmp12 + tmp8) >> shift; ++ blk[3*step] = (tmp1 + tmp10) >> shift; ++ blk[4*step] = (tmp1 - tmp10) >> shift; ++ blk[5*step] = (tmp12 - tmp8) >> shift; ++ blk[6*step] = (tmp11 - tmp7) >> shift; ++ blk[7*step] = (tmp0 - tmp9) >> shift; ++} ++ ++static void bink2g_idct_put(uint8_t *dst, int stride, int16_t *block) ++{ ++ for (int i = 0; i < 8; i++) ++ bink2g_idct_1d(block + i, 8, 0); ++ for (int i = 0; i < 8; i++) ++ bink2g_idct_1d(block + i * 8, 1, 6); ++ for (int i = 0; i < 8; i++) { ++ for (int j = 0; j < 8; j++) ++ dst[j] = av_clip_uint8(block[j * 8 + i]); ++ dst += stride; ++ } ++} ++ ++static void bink2g_idct_add(uint8_t *dst, int stride, int16_t *block) ++{ ++ for (int i = 0; i < 8; i++) ++ bink2g_idct_1d(block + i, 8, 0); ++ for (int i = 0; i < 8; i++) ++ bink2g_idct_1d(block + i * 8, 1, 6); ++ for (int i = 0; i < 8; i++) { ++ for (int j = 0; j < 8; j++) ++ dst[j] = av_clip_uint8(dst[j] + block[j * 8 + i]); ++ dst += stride; ++ } ++} ++ ++static int bink2g_get_type(GetBitContext *gb, int *lru) ++{ ++ int val; ++ ++ switch (get_unary(gb, 1, 3)) { ++ case 0: ++ val = lru[0]; ++ break; ++ case 1: ++ val = lru[1]; ++ FFSWAP(int, lru[0], lru[1]); ++ break; ++ case 2: ++ val = lru[3]; ++ FFSWAP(int, lru[2], lru[3]); ++ break; ++ case 3: ++ val = lru[2]; ++ FFSWAP(int, lru[1], lru[2]); ++ break; ++ } ++ ++ return val; ++} ++ ++static int bink2g_decode_dq(GetBitContext *gb) ++{ ++ int dq = get_unary(gb, 1, 4); ++ ++ if (dq == 3) ++ dq += get_bits1(gb); ++ else if (dq == 4) ++ dq += get_bits(gb, 5) + 1; ++ if (dq && get_bits1(gb)) ++ dq = -dq; ++ ++ return dq; ++} ++ ++static unsigned bink2g_decode_cbp_luma(Bink2Context *c, ++ GetBitContext *gb, unsigned prev_cbp) ++{ ++ unsigned ones = 0, cbp, mask; ++ ++ for (int i = 0; i < 16; i++) { ++ if (prev_cbp & (1 << i)) ++ ones += 1; ++ } ++ ++ cbp = 0; ++ mask = 0; ++ if (ones > 7) { ++ ones = 16 - ones; ++ mask = 0xFFFF; ++ } ++ ++ if (get_bits1(gb) == 0) { ++ if (ones < 4) { ++ for (int j = 0; j < 16; j += 4) ++ if (!get_bits1(gb)) ++ cbp |= get_bits(gb, 4) << j; ++ } else { ++ cbp = get_bits(gb, 16); ++ } ++ } ++ ++ cbp ^= mask; ++ if (!(c->frame_flags & 0x40000) || cbp) { ++ if (get_bits1(gb)) ++ cbp = cbp | cbp << 16; ++ } ++ ++ return cbp; ++} ++ ++static unsigned bink2g_decode_cbp_chroma(GetBitContext *gb, unsigned prev_cbp) ++{ ++ unsigned cbp; ++ ++ cbp = prev_cbp & 0xF0000 | bink2g_chroma_cbp_pat[prev_cbp & 0xF]; ++ if (get_bits1(gb) == 0) { ++ cbp = get_bits(gb, 4); ++ if (get_bits1(gb)) ++ cbp |= cbp << 16; ++ } ++ ++ return cbp; ++} ++ ++static void bink2g_predict_dc(Bink2Context *c, ++ int is_luma, int mindc, int maxdc, ++ int flags, int tdc[16]) ++{ ++ int *LTdc = c->prev_idc[FFMAX(c->mb_pos - 1, 0)].dc[c->comp]; ++ int *Tdc = c->prev_idc[c->mb_pos].dc[c->comp]; ++ int *Ldc = c->current_idc[FFMAX(c->mb_pos - 1, 0)].dc[c->comp]; ++ int *dc = c->current_idc[c->mb_pos].dc[c->comp]; ++ ++ if (is_luma && (flags & 0x20) && (flags & 0x80)) { ++ dc[0] = av_clip((mindc < 0 ? 0 : 1024) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clip(DC_MPRED2(dc[1], dc[3]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clip(dc[4] + tdc[5], mindc, maxdc); ++ dc[6] = av_clip(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clip(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clip(DC_MPRED2(dc[2], dc[3]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clip(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clip(DC_MPRED2(dc[8], dc[9]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clip(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clip(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clip(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clip(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clip(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (is_luma && (flags & 0x80)) { ++ dc[0] = av_clip(DC_MPRED2(Ldc[5], Ldc[7]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED(Ldc[5], Ldc[7], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clip(DC_MPRED2(dc[1], dc[3]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clip(dc[4] + tdc[5], mindc, maxdc); ++ dc[6] = av_clip(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clip(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clip(DC_MPRED(Ldc[7], Ldc[13], dc[2]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clip(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clip(DC_MPRED(Ldc[13], Ldc[15], dc[8]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clip(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clip(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clip(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clip(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clip(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (is_luma && (flags & 0x20)) { ++ dc[0] = av_clip(DC_MPRED2(Tdc[10], Tdc[11]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(DC_MPRED(Tdc[10], dc[0], Tdc[11]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clip(DC_MPRED(Tdc[11], dc[1], Tdc[14]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clip(DC_MPRED(Tdc[14], dc[4], Tdc[15]) + tdc[5], mindc, maxdc); ++ dc[6] = av_clip(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clip(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clip(DC_MPRED2(dc[2], dc[3]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clip(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clip(DC_MPRED2(dc[8], dc[9]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clip(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clip(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clip(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clip(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clip(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (is_luma) { ++ dc[0] = av_clip(DC_MPRED(LTdc[15], Ldc[5], Tdc[10]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(DC_MPRED(Tdc[10], dc[0], Tdc[11]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED(Ldc[5], Ldc[7], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ dc[4] = av_clip(DC_MPRED(Tdc[11], dc[1], Tdc[14]) + tdc[4], mindc, maxdc); ++ dc[5] = av_clip(DC_MPRED(Tdc[14], dc[4], Tdc[15]) + tdc[5], mindc, maxdc); ++ dc[6] = av_clip(DC_MPRED(dc[1], dc[3], dc[4]) + tdc[6], mindc, maxdc); ++ dc[7] = av_clip(DC_MPRED(dc[4], dc[6], dc[5]) + tdc[7], mindc, maxdc); ++ dc[8] = av_clip(DC_MPRED(Ldc[7], Ldc[13], dc[2]) + tdc[8], mindc, maxdc); ++ dc[9] = av_clip(DC_MPRED(dc[2], dc[8], dc[3]) + tdc[9], mindc, maxdc); ++ dc[10] = av_clip(DC_MPRED(Ldc[13], Ldc[15], dc[8]) + tdc[10], mindc, maxdc); ++ dc[11] = av_clip(DC_MPRED(dc[8], dc[10], dc[9]) + tdc[11], mindc, maxdc); ++ dc[12] = av_clip(DC_MPRED(dc[3], dc[9], dc[6]) + tdc[12], mindc, maxdc); ++ dc[13] = av_clip(DC_MPRED(dc[6], dc[12], dc[7]) + tdc[13], mindc, maxdc); ++ dc[14] = av_clip(DC_MPRED(dc[9], dc[11], dc[12]) + tdc[14], mindc, maxdc); ++ dc[15] = av_clip(DC_MPRED(dc[12], dc[14], dc[13]) + tdc[15], mindc, maxdc); ++ } else if (!is_luma && (flags & 0x20) && (flags & 0x80)) { ++ dc[0] = av_clip((mindc < 0 ? 0 : 1024) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } else if (!is_luma && (flags & 0x80)) { ++ dc[0] = av_clip(DC_MPRED2(Ldc[1], Ldc[3]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(dc[0] + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED(Ldc[1], Ldc[3], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } else if (!is_luma && (flags & 0x20)) { ++ dc[0] = av_clip(DC_MPRED2(Tdc[2], Tdc[3]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(DC_MPRED(Tdc[2], dc[0], Tdc[3]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED2(dc[0], dc[1]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } else if (!is_luma) { ++ dc[0] = av_clip(DC_MPRED(LTdc[3], Ldc[1], Tdc[2]) + tdc[0], mindc, maxdc); ++ dc[1] = av_clip(DC_MPRED(Tdc[2], dc[0], Tdc[3]) + tdc[1], mindc, maxdc); ++ dc[2] = av_clip(DC_MPRED(Ldc[1], Ldc[3], dc[0]) + tdc[2], mindc, maxdc); ++ dc[3] = av_clip(DC_MPRED(dc[0], dc[2], dc[1]) + tdc[3], mindc, maxdc); ++ } ++} ++ ++static void bink2g_decode_dc(Bink2Context *c, GetBitContext *gb, int *dc, ++ int is_luma, int q, int mindc, int maxdc, ++ int flags) ++{ ++ const int num_dc = is_luma ? 16 : 4; ++ int tdc[16]; ++ int pat; ++ ++ q = FFMAX(q, 8); ++ pat = bink2g_dc_pat[q]; ++ ++ memset(tdc, 0, sizeof(tdc)); ++ ++ if (get_bits1(gb)) { ++ for (int i = 0; i < num_dc; i++) { ++ int cnt = get_unary(gb, 0, 12); ++ ++ if (cnt > 3) ++ cnt = (1 << (cnt - 3)) + get_bits(gb, cnt - 3) + 2; ++ if (cnt && get_bits1(gb)) ++ cnt = -cnt; ++ tdc[i] = (cnt * pat + 0x1FF) >> 10; ++ } ++ } ++ ++ bink2g_predict_dc(c, is_luma, mindc, maxdc, flags, tdc); ++} ++ ++static int bink2g_decode_ac(GetBitContext *gb, const uint8_t scan[64], ++ int16_t block[4][64], unsigned cbp, ++ int q, const uint16_t qmat[4][64], ++ uint8_t ac_count[4]) ++{ ++ int idx, next, val, skip; ++ VLC *skip_vlc; ++ ++ for (int i = 0; i < 4; i++) { ++ memset(block[i], 0, sizeof(int16_t) * 64); ++ ac_count[i] = 0; ++ } ++ ++ if ((cbp & 0xf) == 0) ++ return 0; ++ ++ skip_vlc = &bink2g_ac_skip0_vlc; ++ if (cbp & 0xffff0000) ++ skip_vlc = &bink2g_ac_skip1_vlc; ++ ++ for (int i = 0; i < 4; i++, cbp >>= 1) { ++ if (!(cbp & 1)) ++ continue; ++ ++ next = 0; ++ idx = 1; ++ while (idx < 64) { ++ next--; ++ if (next < 1) { ++ skip = get_vlc2(gb, skip_vlc->table, skip_vlc->bits, 1); ++ if (skip < 0) ++ return AVERROR_INVALIDDATA; ++ next = bink2_next_skips[skip]; ++ skip = bink2g_skips[skip]; ++ if (skip == 11) ++ skip = get_bits(gb, 6); ++ idx += skip; ++ if (idx >= 64) ++ break; ++ } ++ ++ val = get_unary(gb, 0, 12) + 1; ++ if (val > 3) ++ val = get_bits(gb, val - 3) + (1 << (val - 3)) + 2; ++ if (get_bits1(gb)) ++ val = -val; ++ block[i][scan[idx]] = ((val * qmat[q & 3][scan[idx]] * (1 << (q >> 2))) + 64) >> 7; ++ ac_count[i]++; ++ idx++; ++ } ++ } ++ ++ return 0; ++} ++ ++static inline uint16_t bink2g_block_state(int dc, unsigned ac_count, int intra) ++{ ++ const unsigned strength = (ac_count < 8) + (ac_count < 4) + !ac_count; ++ ++ return (dc & 0x1FFF) | (strength << 13) | (intra ? 0x8000 : 0); ++} ++ ++static int bink2g_decode_intra_luma(Bink2Context *c, ++ GetBitContext *gb, int16_t block[4][64], ++ unsigned *prev_cbp, int q, ++ BlockDSPContext *dsp, uint8_t *dst, int stride, ++ int flags) ++{ ++ int *dc = c->current_idc[c->mb_pos].dc[c->comp]; ++ uint16_t *state = c->current_idc[c->mb_pos].state[c->comp]; ++ unsigned cbp; ++ int ret; ++ ++ *prev_cbp = cbp = bink2g_decode_cbp_luma(c, gb, *prev_cbp); ++ ++ bink2g_decode_dc(c, gb, dc, 1, q, 0, 2047, flags); ++ ++ for (int i = 0; i < 4; i++) { ++ uint8_t ac_count[4]; ++ ++ ret = bink2g_decode_ac(gb, bink2g_scan, block, cbp >> (4*i), ++ q, bink2g_luma_intra_qmat, ac_count); ++ if (ret < 0) ++ return ret; ++ ++ for (int j = 0; j < 4; j++) { ++ const int block_index = i * 4 + j; ++ const int raster_index = luma_repos[block_index]; ++ ++ state[raster_index] = bink2g_block_state(dc[block_index], ac_count[j], 1); ++ block[j][0] = dc[block_index] * 8 + 32; ++ bink2g_idct_put(dst + (raster_index & 3) * 8 + ++ (raster_index >> 2) * 8 * stride, stride, block[j]); ++ } ++ } ++ ++ return 0; ++} ++ ++static int bink2g_decode_intra_chroma(Bink2Context *c, ++ GetBitContext *gb, int16_t block[4][64], ++ unsigned *prev_cbp, int q, ++ BlockDSPContext *dsp, uint8_t *dst, int stride, ++ int flags) ++{ ++ int *dc = c->current_idc[c->mb_pos].dc[c->comp]; ++ uint16_t *state = c->current_idc[c->mb_pos].state[c->comp]; ++ uint8_t ac_count[4]; ++ unsigned cbp; ++ int ret; ++ ++ *prev_cbp = cbp = bink2g_decode_cbp_chroma(gb, *prev_cbp); ++ ++ bink2g_decode_dc(c, gb, dc, 0, q, 0, 2047, flags); ++ ++ ret = bink2g_decode_ac(gb, bink2g_scan, block, cbp, ++ q, bink2g_chroma_intra_qmat, ac_count); ++ if (ret < 0) ++ return ret; ++ ++ for (int j = 0; j < 4; j++) { ++ state[j] = bink2g_block_state(dc[j], ac_count[j], 1); ++ block[j][0] = dc[j] * 8 + 32; ++ bink2g_idct_put(dst + (j & 1) * 8 + ++ (j >> 1) * 8 * stride, stride, block[j]); ++ } ++ ++ return 0; ++} ++ ++static int bink2g_decode_inter_luma(Bink2Context *c, ++ GetBitContext *gb, int16_t block[4][64], ++ unsigned *prev_cbp, int q, ++ BlockDSPContext *dsp, uint8_t *dst, int stride, ++ int flags) ++{ ++ int *dc = c->current_idc[c->mb_pos].dc[c->comp]; ++ uint16_t *state = c->current_idc[c->mb_pos].state[c->comp]; ++ const int maxdc = c->frame_flags & 0x40000 ? 2047 : 1023; ++ unsigned cbp; ++ int ret; ++ ++ *prev_cbp = cbp = bink2g_decode_cbp_luma(c, gb, *prev_cbp); ++ ++ bink2g_decode_dc(c, gb, dc, 1, q, -maxdc, maxdc, 0xA8); ++ ++ for (int i = 0; i < 4; i++) { ++ uint8_t ac_count[4]; ++ ++ ret = bink2g_decode_ac(gb, bink2g_scan, block, cbp >> (4 * i), ++ q, bink2g_inter_qmat, ac_count); ++ if (ret < 0) ++ return ret; ++ ++ for (int j = 0; j < 4; j++) { ++ const int block_index = i * 4 + j; ++ const int raster_index = luma_repos[block_index]; ++ ++ state[raster_index] = bink2g_block_state(dc[block_index], ac_count[j], 0); ++ block[j][0] = dc[block_index] * 8 + 32; ++ bink2g_idct_add(dst + (raster_index & 3) * 8 + ++ (raster_index >> 2) * 8 * stride, ++ stride, block[j]); ++ } ++ } ++ ++ return 0; ++} ++ ++static int bink2g_decode_inter_chroma(Bink2Context *c, ++ GetBitContext *gb, int16_t block[4][64], ++ unsigned *prev_cbp, int q, ++ BlockDSPContext *dsp, uint8_t *dst, int stride, ++ int flags) ++{ ++ int *dc = c->current_idc[c->mb_pos].dc[c->comp]; ++ uint16_t *state = c->current_idc[c->mb_pos].state[c->comp]; ++ uint8_t ac_count[4]; ++ const int maxdc = c->frame_flags & 0x40000 ? 2047 : 1023; ++ unsigned cbp; ++ int ret; ++ ++ *prev_cbp = cbp = bink2g_decode_cbp_chroma(gb, *prev_cbp); ++ ++ bink2g_decode_dc(c, gb, dc, 0, q, -maxdc, maxdc, 0xA8); ++ ++ ret = bink2g_decode_ac(gb, bink2g_scan, block, cbp, ++ q, bink2g_inter_qmat, ac_count); ++ if (ret < 0) ++ return ret; ++ ++ for (int j = 0; j < 4; j++) { ++ state[j] = bink2g_block_state(dc[j], ac_count[j], 0); ++ block[j][0] = dc[j] * 8 + 32; ++ bink2g_idct_add(dst + (j & 1) * 8 + ++ (j >> 1) * 8 * stride, stride, block[j]); ++ } ++ ++ return 0; ++} ++ ++static void bink2g_predict_mv(Bink2Context *c, int x, int y, int flags, MVectors mv) ++{ ++ MVectors *c_mv = &c->current_mv[c->mb_pos].mv; ++ MVectors *l_mv = &c->current_mv[FFMAX(c->mb_pos - 1, 0)].mv; ++ MVectors *lt_mv = &c->prev_mv[FFMAX(c->mb_pos - 1, 0)].mv; ++ MVectors *t_mv = &c->prev_mv[c->mb_pos].mv; ++ ++ if (mv.nb_vectors == 1) { ++ if (flags & 0x80) { ++ if (!(flags & 0x20)) { ++ mv.v[0][0] += mid_pred(l_mv->v[0][0], l_mv->v[1][0], l_mv->v[3][0]); ++ mv.v[0][1] += mid_pred(l_mv->v[0][1], l_mv->v[1][1], l_mv->v[3][1]); ++ } ++ } else { ++ if (!(flags & 0x20)) { ++ mv.v[0][0] += mid_pred(lt_mv->v[3][0], t_mv->v[2][0], l_mv->v[1][0]); ++ mv.v[0][1] += mid_pred(lt_mv->v[3][1], t_mv->v[2][1], l_mv->v[1][1]); ++ } else { ++ mv.v[0][0] += mid_pred(t_mv->v[0][0], t_mv->v[2][0], t_mv->v[3][0]); ++ mv.v[0][1] += mid_pred(t_mv->v[0][1], t_mv->v[2][1], t_mv->v[3][1]); ++ } ++ } ++ ++ c_mv->v[0][0] = mv.v[0][0]; ++ c_mv->v[0][1] = mv.v[0][1]; ++ c_mv->v[1][0] = mv.v[0][0]; ++ c_mv->v[1][1] = mv.v[0][1]; ++ c_mv->v[2][0] = mv.v[0][0]; ++ c_mv->v[2][1] = mv.v[0][1]; ++ c_mv->v[3][0] = mv.v[0][0]; ++ c_mv->v[3][1] = mv.v[0][1]; ++ ++ return; ++ } ++ ++ if (!(flags & 0x80)) { ++ if (flags & 0x20) { ++ c_mv->v[0][0] = mv.v[0][0] + mid_pred(t_mv->v[0][0], t_mv->v[2][0], t_mv->v[3][0]); ++ c_mv->v[0][1] = mv.v[0][1] + mid_pred(t_mv->v[0][1], t_mv->v[2][1], t_mv->v[3][1]); ++ c_mv->v[1][0] = mv.v[1][0] + mid_pred(t_mv->v[2][0], t_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[1][1] = mv.v[1][1] + mid_pred(t_mv->v[2][1], t_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[2][0] = mv.v[2][0] + mid_pred(t_mv->v[2][0], c_mv->v[0][0], c_mv->v[1][0]); ++ c_mv->v[2][1] = mv.v[2][1] + mid_pred(t_mv->v[2][1], c_mv->v[0][1], c_mv->v[1][1]); ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred(c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred(c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } else { ++ c_mv->v[0][0] = mv.v[0][0] + mid_pred(t_mv->v[2][0], lt_mv->v[3][0], l_mv->v[1][0]); ++ c_mv->v[0][1] = mv.v[0][1] + mid_pred(t_mv->v[2][1], lt_mv->v[3][1], l_mv->v[1][1]); ++ c_mv->v[1][0] = mv.v[1][0] + mid_pred(t_mv->v[2][0], t_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[1][1] = mv.v[1][1] + mid_pred(t_mv->v[2][1], t_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[2][0] = mv.v[2][0] + mid_pred(l_mv->v[1][0], l_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[2][1] = mv.v[2][1] + mid_pred(l_mv->v[1][1], l_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred(c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred(c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } ++ } else { ++ if (flags & 0x20) { ++ c_mv->v[0][0] = mv.v[0][0]; ++ c_mv->v[0][1] = mv.v[0][1]; ++ c_mv->v[1][0] = mv.v[1][0] + mv.v[0][0]; ++ c_mv->v[1][1] = mv.v[1][1] + mv.v[0][1]; ++ c_mv->v[2][0] = mv.v[2][0] + mv.v[0][0]; ++ c_mv->v[2][1] = mv.v[2][1] + mv.v[0][1]; ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred(c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred(c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } else { ++ const int left_v1 = mv.nb_vectors == 0 ? 3 : 1; ++ ++ c_mv->v[0][0] = mv.v[0][0] + mid_pred(l_mv->v[0][0], l_mv->v[1][0], l_mv->v[3][0]); ++ c_mv->v[0][1] = mv.v[0][1] + mid_pred(l_mv->v[0][1], l_mv->v[1][1], l_mv->v[3][1]); ++ c_mv->v[2][0] = mv.v[2][0] + mid_pred(l_mv->v[1][0], l_mv->v[3][0], c_mv->v[0][0]); ++ c_mv->v[2][1] = mv.v[2][1] + mid_pred(l_mv->v[1][1], l_mv->v[3][1], c_mv->v[0][1]); ++ c_mv->v[1][0] = mv.v[1][0] + mid_pred(l_mv->v[left_v1][0], c_mv->v[0][0], c_mv->v[2][0]); ++ c_mv->v[1][1] = mv.v[1][1] + mid_pred(l_mv->v[left_v1][1], c_mv->v[0][1], c_mv->v[2][1]); ++ c_mv->v[3][0] = mv.v[3][0] + mid_pred(c_mv->v[0][0], c_mv->v[1][0], c_mv->v[2][0]); ++ c_mv->v[3][1] = mv.v[3][1] + mid_pred(c_mv->v[0][1], c_mv->v[1][1], c_mv->v[2][1]); ++ } ++ } ++} ++ ++static int bink2g_decode_mv(Bink2Context *c, GetBitContext *gb, int x, int y, ++ MVectors *mv) ++{ ++ int num_mvs = get_bits1(gb) ? 1 : 4; ++ ++ mv->nb_vectors = num_mvs; ++ ++ for (int i = 0; i < 2; i++) { ++ for (int j = 0; j < num_mvs; j++) { ++ int val = get_vlc2(gb, bink2g_mv_vlc.table, bink2g_mv_vlc.bits, 1); ++ ++ if (val < 0) ++ return AVERROR_INVALIDDATA; ++ if (val >= 8 && val != 15) ++ val = val - 15; ++ if (val == 15) { ++ int bits = get_unary(gb, 1, 12) + 4; ++ val = get_bits(gb, bits) + (1 << bits) - 1; ++ if (val & 1) ++ val = (-(val >> 1) - 1); ++ else ++ val = val >> 1; ++ } ++ mv->v[j][i] = val; ++ } ++ } ++ ++ return 0; ++} ++ ++static void update_intra_q(Bink2Context *c, int8_t *intra_q, int dq, int flags) ++{ ++ if (flags & 0x20 && flags & 0x80) ++ *intra_q = 16 + dq; ++ else if (flags & 0x80) ++ *intra_q = c->current_q[c->mb_pos - 1].intra_q + dq; ++ else if (flags & 0x20) ++ *intra_q = c->prev_q[c->mb_pos].intra_q + dq; ++ else ++ *intra_q = mid_pred(c->prev_q[c->mb_pos].intra_q, ++ c->current_q[c->mb_pos - 1].intra_q, ++ c->prev_q[c->mb_pos - 1].intra_q) + dq; ++} ++ ++static void update_inter_q(Bink2Context *c, int8_t *inter_q, int dq, int flags) ++{ ++ if (flags & 0x20 && flags & 0x80) ++ *inter_q = 16 + dq; ++ else if (flags & 0x80) ++ *inter_q = c->current_q[c->mb_pos - 1].inter_q + dq; ++ else if (flags & 0x20) ++ *inter_q = c->prev_q[c->mb_pos].inter_q + dq; ++ else ++ *inter_q = mid_pred(c->prev_q[c->mb_pos].inter_q, ++ c->current_q[c->mb_pos - 1].inter_q, ++ c->prev_q[c->mb_pos - 1].inter_q) + dq; ++} ++ ++static const uint8_t bink2g_chroma_weights[4][2] = { ++ { 1, 0 }, ++ { 3, 1 }, ++ { 1, 1 }, ++ { 1, 3 }, ++}; ++ ++static const uint8_t bink2g_chroma_shifts[4] = { 0, 2, 1, 2 }; ++ ++static void bink2g_c_mc(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height, ++ int mv_x, int mv_y, ++ int mode) ++{ ++ const uint8_t *msrc; ++ int hphase, vphase, shift, rounding; ++ ++ if (mv_x < 0 || mv_x >= width || ++ mv_y < 0 || mv_y >= height) ++ return; ++ ++ msrc = src + mv_x + mv_y * sstride; ++ ++ if (mode == 0) { ++ copy_block8(dst, msrc, stride, sstride, 8); ++ return; ++ } ++ ++ hphase = mode & 3; ++ vphase = mode >> 2; ++ shift = bink2g_chroma_shifts[hphase] + bink2g_chroma_shifts[vphase]; ++ rounding = 1 << (shift - 1); ++ ++ for (int y = 0; y < 8; y++) { ++ const uint8_t *top = msrc + y * sstride; ++ const uint8_t *bottom = top + (vphase != 0) * sstride; ++ ++ for (int x = 0; x < 8; x++) { ++ const int top_value = bink2g_chroma_weights[hphase][0] * top[x] + ++ bink2g_chroma_weights[hphase][1] * top[x + (hphase != 0)]; ++ const int bottom_value = bink2g_chroma_weights[hphase][0] * bottom[x] + ++ bink2g_chroma_weights[hphase][1] * bottom[x + (hphase != 0)]; ++ const int value = bink2g_chroma_weights[vphase][0] * top_value + ++ bink2g_chroma_weights[vphase][1] * bottom_value; ++ ++ dst[x] = (value + rounding) >> shift; ++ } ++ dst += stride; ++ } ++} ++ ++static void bink2g_filter_vertical_edge(uint8_t *p, int stride, ++ unsigned left, unsigned right) ++{ ++ const unsigned index = left | (right << 2); ++ ++ if (!index || index == 15) ++ return; ++ ++ for (int y = 0; y < 8; y++, p += stride) { ++ const int b = p[-1]; ++ const int c = p[0]; ++ const int delta = c - b; ++ const int outer = (2 * delta + 8) >> 4; ++ const int inner = (4 * delta + 8) >> 4; ++ ++ if (left >= 2) ++ p[-2] = av_clip_uint8(p[-2] + outer); ++ if (left >= 1) ++ p[-1] = av_clip_uint8(b + inner); ++ if (right >= 1) ++ p[0] = av_clip_uint8(c - inner); ++ if (right >= 2) ++ p[1] = av_clip_uint8(p[1] - outer); ++ } ++} ++ ++static void bink2g_filter_horizontal_edge(uint8_t *p, int stride, ++ unsigned top, unsigned bottom) ++{ ++ const unsigned index = top | (bottom << 2); ++ ++ if (!index || index == 15) ++ return; ++ ++ for (int x = 0; x < 8; x++, p++) { ++ const int b = p[-stride]; ++ const int c = p[0]; ++ const int delta = c - b; ++ const int outer = (2 * delta + 8) >> 4; ++ const int inner = (4 * delta + 8) >> 4; ++ ++ if (top >= 2) ++ p[-2 * stride] = av_clip_uint8(p[-2 * stride] + outer); ++ if (top >= 1) ++ p[-stride] = av_clip_uint8(b + inner); ++ if (bottom >= 1) ++ p[0] = av_clip_uint8(c - inner); ++ if (bottom >= 2) ++ p[stride] = av_clip_uint8(p[stride] - outer); ++ } ++} ++ ++static int bink2g_mcompensate_chroma(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height) ++{ ++ MVectors *mv = &c->current_mv[c->mb_pos].mv; ++ int mv_x, mv_y, mode; ++ ++ mv_x = (mv->v[0][0] >> 2) + x; ++ mv_y = (mv->v[0][1] >> 2) + y; ++ mode = mv->v[0][0] & 3; ++ mode |= (mv->v[0][1] & 3) << 2; ++ bink2g_c_mc(c, x, y, dst + x, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[1][0] >> 2) + x + 8; ++ mv_y = (mv->v[1][1] >> 2) + y; ++ mode = mv->v[1][0] & 3; ++ mode |= (mv->v[1][1] & 3) << 2; ++ bink2g_c_mc(c, x, y, dst + x + 8, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[2][0] >> 2) + x; ++ mv_y = (mv->v[2][1] >> 2) + y + 8; ++ mode = mv->v[2][0] & 3; ++ mode |= (mv->v[2][1] & 3) << 2; ++ bink2g_c_mc(c, x, y, dst + x + 8 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[3][0] >> 2) + x + 8; ++ mv_y = (mv->v[3][1] >> 2) + y + 8; ++ mode = mv->v[3][0] & 3; ++ mode |= (mv->v[3][1] & 3) << 2; ++ bink2g_c_mc(c, x, y, dst + x + 8 + 8 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ return 0; ++} ++ ++static void bink2g_y_mc(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height, ++ int mv_x, int mv_y, int mode) ++{ ++ uint8_t *msrc; ++ ++ if (mv_x < 0 || mv_x >= width || ++ mv_y < 0 || mv_y >= height) ++ return; ++ ++ msrc = src + mv_x + mv_y * sstride; ++ ++ if (mode == 0) { ++ copy_block16(dst, msrc, stride, sstride, 16); ++ } else if (mode == 1) { ++ for (int j = 0; j < 16; j++) { ++ for (int i = 0; i < 16; i++) ++ dst[i] = av_clip_uint8(LHFILTER(msrc + i)); ++ dst += stride; ++ msrc += sstride; ++ } ++ } else if (mode == 2) { ++ for (int j = 0; j < 16; j++) { ++ for (int i = 0; i < 16; i++) ++ dst[i*stride] = av_clip_uint8(LVFILTER(msrc + i*sstride, sstride)); ++ dst += 1; ++ msrc += 1; ++ } ++ } else if (mode == 3) { ++ int16_t temp[21 * 16]; ++ ++ msrc -= 2 * sstride; ++ for (int i = 0; i < 21; i++) { ++ for (int j = 0; j < 16; j++) ++ temp[i*16+j] = LHFILTER(msrc + j); ++ msrc += sstride; ++ } ++ for (int j = 0; j < 16; j++) { ++ for (int i = 0; i < 16; i++) ++ dst[i] = av_clip_uint8(LVFILTER(temp+(j+2)*16+i, 16)); ++ dst += stride; ++ } ++ } ++} ++ ++static int bink2g_mcompensate_luma(Bink2Context *c, int x, int y, ++ uint8_t *dst, int stride, ++ uint8_t *src, int sstride, ++ int width, int height) ++{ ++ MVectors *mv = &c->current_mv[c->mb_pos].mv; ++ int mv_x, mv_y, mode; ++ ++ mv_x = (mv->v[0][0] >> 1) + x; ++ mv_y = (mv->v[0][1] >> 1) + y; ++ mode = mv->v[0][0] & 1; ++ mode |= (mv->v[0][1] & 1) << 1; ++ bink2g_y_mc(c, x, y, dst + x, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[1][0] >> 1) + x + 16; ++ mv_y = (mv->v[1][1] >> 1) + y; ++ mode = mv->v[1][0] & 1; ++ mode |= (mv->v[1][1] & 1) << 1; ++ bink2g_y_mc(c, x, y, dst + x + 16, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[2][0] >> 1) + x; ++ mv_y = (mv->v[2][1] >> 1) + y + 16; ++ mode = mv->v[2][0] & 1; ++ mode |= (mv->v[2][1] & 1) << 1; ++ bink2g_y_mc(c, x, y, dst + x + 16 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ mv_x = (mv->v[3][0] >> 1) + x + 16; ++ mv_y = (mv->v[3][1] >> 1) + y + 16; ++ mode = mv->v[3][0] & 1; ++ mode |= (mv->v[3][1] & 1) << 1; ++ bink2g_y_mc(c, x, y, dst + x + 16 + 16 * stride, stride, src, sstride, width, height, mv_x, mv_y, mode); ++ ++ return 0; ++} ++ ++static int bink2g_average_block(uint8_t *src, int stride) ++{ ++ int sum = 0; ++ ++ for (int i = 0; i < 8; i++) { ++ int avg_a = (src[i+0*stride] + src[i+1*stride] + 1) >> 1; ++ int avg_b = (src[i+2*stride] + src[i+3*stride] + 1) >> 1; ++ int avg_c = (src[i+4*stride] + src[i+5*stride] + 1) >> 1; ++ int avg_d = (src[i+6*stride] + src[i+7*stride] + 1) >> 1; ++ int avg_e = (avg_a + avg_b + 1) >> 1; ++ int avg_f = (avg_c + avg_d + 1) >> 1; ++ int avg_g = (avg_e + avg_f + 1) >> 1; ++ sum += avg_g; ++ } ++ ++ return sum; ++} ++ ++static void bink2g_average_chroma(Bink2Context *c, int x, int y, ++ uint8_t *src, int stride, ++ int *dc) ++{ ++ for (int i = 0; i < 4; i++) { ++ int X = i & 1; ++ int Y = i >> 1; ++ dc[i] = bink2g_average_block(src + x + X * 8 + (y + Y * 8) * stride, stride); ++ } ++} ++ ++static void bink2g_average_luma(Bink2Context *c, int x, int y, ++ uint8_t *src, int stride, ++ int *dc) ++{ ++ for (int i = 0; i < 16; i++) { ++ int I = luma_repos[i]; ++ int X = I & 3; ++ int Y = I >> 2; ++ dc[i] = bink2g_average_block(src + x + X * 8 + (y + Y * 8) * stride, stride); ++ } ++} ++ ++static inline unsigned bink2g_filter_strength(uint16_t state) ++{ ++ return (state >> 13) & 3; ++} ++ ++static inline int bink2g_filter_map_skip(const uint8_t *map, int position) ++{ ++ const unsigned bit = position >> 3; ++ ++ return (map[bit >> 3] >> (bit & 7)) & 1; ++} ++ ++static inline int bink2g_mv_discontinuity(const MVectors *a, int ai, ++ const MVectors *b, int bi) ++{ ++ return FFABS(a->v[ai][0] - b->v[bi][0]) > 1 || ++ FFABS(a->v[ai][1] - b->v[bi][1]) > 1; ++} ++ ++static inline void bink2g_force_mv_edge(const MVectors *a, int ai, ++ const MVectors *b, int bi, ++ uint16_t left_state, ++ uint16_t right_state, ++ unsigned *left, unsigned *right) ++{ ++ if (!(left_state & 0x8000) && !(right_state & 0x8000) && ++ bink2g_mv_discontinuity(a, ai, b, bi)) ++ *left = *right = 2; ++} ++ ++static inline void bink2g_select_coding_edge(uint16_t left_state, ++ uint16_t right_state, ++ unsigned *left, ++ unsigned *right) ++{ ++ const int left_intra = !!(left_state & 0x8000); ++ const int right_intra = !!(right_state & 0x8000); ++ ++ if (!left_intra) ++ *left = 0; ++ if (!right_intra) ++ *right = 0; ++} ++ ++static inline int bink2g_recon_near(uint16_t a, uint16_t b, int threshold) ++{ ++ const int16_t ab = (int16_t)(uint16_t)(a - b); ++ const int16_t ba = (int16_t)(uint16_t)(b - a); ++ ++ return FFMAX(ab, ba) < threshold; ++} ++ ++static inline int bink2g_recon_mulhi(int k, int v) ++{ ++ return ((int32_t)(int16_t)k * (int16_t)v) >> 16; ++} ++ ++static inline int bink2g_recon_curve(int base, int left, int right, int pos) ++{ ++ switch (pos) { ++ case 0: return base + bink2g_recon_mulhi(26624, left); ++ case 1: return base + bink2g_recon_mulhi(18432, left) + (right >> 7); ++ case 2: return base + bink2g_recon_mulhi(12288, left) + (right >> 5); ++ case 3: return base + bink2g_recon_mulhi( 7168, left) + (right >> 4); ++ case 4: return base + bink2g_recon_mulhi( 7168, right) + (left >> 4); ++ case 5: return base + bink2g_recon_mulhi(12288, right) + (left >> 5); ++ case 6: return base + bink2g_recon_mulhi(18432, right) + (left >> 7); ++ default:return base + bink2g_recon_mulhi(26624, right); ++ } ++} ++ ++static inline uint16_t bink2g_recon_state(const DCIPredict *row, int comp, ++ int blocks_per_mb, int blocks_w, ++ int block_x, int block_y) ++{ ++ block_x = av_clip(block_x, 0, blocks_w - 1); ++ return row[block_x / blocks_per_mb].state[comp] ++ [block_y * blocks_per_mb + block_x % blocks_per_mb]; ++} ++ ++static void bink2g_reconstruct_block(uint8_t *dst, int stride, ++ int width, int height, ++ uint16_t left, uint16_t target, ++ uint16_t right, uint16_t up_left, ++ uint16_t up, uint16_t up_right, ++ uint16_t down_left, uint16_t down, ++ uint16_t down_right, int threshold) ++{ ++ int center[8], top[8], bottom[8]; ++ int d, l, r, u, v, ul, ur, dl, dr; ++ ++ if (target < 0xE000 || width <= 0 || height <= 0) ++ return; ++ ++ d = target & 0x1FFF; ++ l = bink2g_recon_near(target, left, threshold) ? left & 0x1FFF : d; ++ r = bink2g_recon_near(target, right, threshold) ? right & 0x1FFF : d; ++ u = bink2g_recon_near(target, up, threshold) ? up & 0x1FFF : d; ++ v = bink2g_recon_near(target, down, threshold) ? down & 0x1FFF : d; ++ ul = bink2g_recon_near(target, up_left, threshold) ? up_left & 0x1FFF : l; ++ ur = bink2g_recon_near(target, up_right, threshold) ? up_right & 0x1FFF : r; ++ dl = bink2g_recon_near(target, down_left, threshold) ? down_left & 0x1FFF : l; ++ dr = bink2g_recon_near(target, down_right, threshold) ? down_right & 0x1FFF : r; ++ ++ for (int x = 0; x < 8; x++) { ++ center[x] = bink2g_recon_curve(d + 4, l - d, r - d, x); ++ top[x] = bink2g_recon_curve(u + 4, ul - u, ur - u, x) - center[x]; ++ bottom[x] = bink2g_recon_curve(v + 4, dl - v, dr - v, x) - center[x]; ++ } ++ ++ for (int y = 0; y < FFMIN(height, 8); y++) { ++ for (int x = 0; x < FFMIN(width, 8); x++) ++ dst[x] = av_clip_uint8(bink2g_recon_curve(center[x], top[x], ++ bottom[x], y) >> 3); ++ dst += stride; ++ } ++} ++ ++static void bink2g_reconstruct_blocks(Bink2Context *c, uint8_t *dst, ++ int stride, int comp, int global_y, ++ const DCIPredict *up_row, int up_by, ++ const DCIPredict *row, int by, ++ const DCIPredict *down_row, int down_by) ++{ ++ const int chroma = comp == 1 || comp == 2; ++ const int blocks_per_mb = chroma ? 2 : 4; ++ const int plane_w = chroma ? (c->avctx->width + 1) >> 1 : c->avctx->width; ++ const int plane_h = chroma ? (c->avctx->height + 1) >> 1 : c->avctx->height; ++ const int visible_blocks_w = (plane_w + 7) >> 3; ++ const int coded_blocks_w = ((c->avctx->width + 31) >> 5) * blocks_per_mb; ++ const int coded_plane_h = ((c->avctx->height + 31) >> 5) * ++ blocks_per_mb * 8; ++ const int threshold = (c->frame_flags & 0xFF) << 3; ++ const int have_up = global_y >= 8; ++ const int have_down = global_y + 8 < coded_plane_h; ++ ++ for (int bx = 0; bx < visible_blocks_w; bx++) { ++ const uint16_t target = bink2g_recon_state(row, comp, blocks_per_mb, ++ coded_blocks_w, bx, by); ++ const uint16_t left = bx ? ++ bink2g_recon_state(row, comp, blocks_per_mb, ++ coded_blocks_w, bx - 1, by) : target; ++ const uint16_t right = bx + 1 < coded_blocks_w ? ++ bink2g_recon_state(row, comp, blocks_per_mb, ++ coded_blocks_w, bx + 1, by) : target; ++ const uint16_t up_left = have_up && bx ? ++ bink2g_recon_state(up_row, comp, blocks_per_mb, ++ coded_blocks_w, bx - 1, up_by) : 0; ++ const uint16_t up = have_up ? ++ bink2g_recon_state(up_row, comp, blocks_per_mb, ++ coded_blocks_w, bx, up_by) : target; ++ const uint16_t up_right = have_up && bx + 1 < coded_blocks_w ? ++ bink2g_recon_state(up_row, comp, blocks_per_mb, ++ coded_blocks_w, bx + 1, up_by) : 0; ++ const uint16_t down_left = bx ? ++ have_down ? ++ bink2g_recon_state(down_row, comp, blocks_per_mb, ++ coded_blocks_w, bx - 1, down_by) : ++ (target & 0xE000) | (left & 0x1FFF) : 0; ++ const uint16_t down = have_down ? ++ bink2g_recon_state(down_row, comp, blocks_per_mb, ++ coded_blocks_w, bx, down_by) : target; ++ const uint16_t down_right = bx + 1 < coded_blocks_w ? ++ have_down ? ++ bink2g_recon_state(down_row, comp, blocks_per_mb, ++ coded_blocks_w, bx + 1, down_by) : ++ (target & 0xE000) | (right & 0x1FFF) : 0; ++ ++ bink2g_reconstruct_block(dst + bx * 8, stride, ++ plane_w - bx * 8, plane_h - global_y, ++ left, target, right, up_left, up, up_right, ++ down_left, down, down_right, threshold); ++ } ++} ++ ++static void bink2g_reconstruct_row(Bink2Context *c, uint8_t *dst[4], ++ const int stride[4], int y, ++ const DCIPredict *prev_idc, ++ const DCIPredict *current_idc, ++ int is_last) ++{ ++ for (int comp = 0; comp < 3 + c->has_alpha; comp++) { ++ const int chroma = comp == 1 || comp == 2; ++ const int plane = comp == 1 ? 2 : comp == 2 ? 1 : comp; ++ const int blocks_per_mb = chroma ? 2 : 4; ++ const int plane_y = chroma ? y >> 1 : y; ++ ++ if (y > 0) { ++ const int by = blocks_per_mb - 1; ++ ++ bink2g_reconstruct_blocks(c, dst[plane] - 8 * stride[plane], ++ stride[plane], comp, plane_y - 8, ++ prev_idc, by - 1, ++ prev_idc, by, ++ current_idc, 0); ++ } ++ ++ for (int by = 0; by < blocks_per_mb - 1; by++) { ++ const DCIPredict *up_row = by ? current_idc : ++ y > 0 ? prev_idc : current_idc; ++ const int up_by = by ? by - 1 : ++ y > 0 ? blocks_per_mb - 1 : 0; ++ ++ bink2g_reconstruct_blocks(c, dst[plane] + by * 8 * stride[plane], ++ stride[plane], comp, plane_y + by * 8, ++ up_row, up_by, ++ current_idc, by, ++ current_idc, by + 1); ++ } ++ ++ if (is_last) { ++ const int by = blocks_per_mb - 1; ++ ++ bink2g_reconstruct_blocks(c, dst[plane] + by * 8 * stride[plane], ++ stride[plane], comp, plane_y + by * 8, ++ current_idc, by - 1, ++ current_idc, by, ++ current_idc, by); ++ } ++ } ++} ++ ++static void bink2g_filter_vertical_row(Bink2Context *c, uint8_t *dst[4], ++ const int stride[4], int comp, ++ const DCIPredict *row, ++ const MVPredict *mv_row, ++ int by, int rel_y) ++{ ++ const int chroma = comp != 0; ++ const int plane = comp == 1 ? 2 : comp == 2 ? 1 : 0; ++ const int blocks_per_mb = chroma ? 2 : 4; ++ const int plane_w = chroma ? (c->avctx->width + 1) >> 1 : c->avctx->width; ++ const int blocks_w = (plane_w + 7) >> 3; ++ ++ if (c->frame_flags & 0x4000) ++ return; ++ ++ for (int bx = 1; bx < blocks_w; bx++) { ++ const int mb = bx / blocks_per_mb; ++ const int local_x = bx % blocks_per_mb; ++ const int left_mb = local_x ? mb : mb - 1; ++ const int left_x = local_x ? local_x - 1 : blocks_per_mb - 1; ++ const uint16_t left_state = row[left_mb].state[comp] ++ [by * blocks_per_mb + left_x]; ++ const uint16_t right_state = row[mb].state[comp] ++ [by * blocks_per_mb + local_x]; ++ unsigned left = bink2g_filter_strength(left_state); ++ unsigned right = bink2g_filter_strength(right_state); ++ const int edge_x = bx * (chroma ? 16 : 8); ++ ++ bink2g_select_coding_edge(left_state, right_state, &left, &right); ++ ++ if (edge_x >= c->avctx->width || ++ bink2g_filter_map_skip(c->col_cbp, edge_x)) ++ continue; ++ ++ if (chroma || !(bx & 1)) { ++ const int qrow = chroma ? by : by >> 1; ++ const int left_q = 2 * qrow + (local_x ? 0 : 1); ++ const int right_q = 2 * qrow + (local_x ? 1 : 0); ++ ++ bink2g_force_mv_edge(&mv_row[left_mb].mv, left_q, ++ &mv_row[mb].mv, right_q, ++ left_state, right_state, &left, &right); ++ } ++ ++ bink2g_filter_vertical_edge(dst[plane] + bx * 8 + ++ rel_y * stride[plane], ++ stride[plane], left, right); ++ } ++} ++ ++static void bink2g_filter_horizontal_row(Bink2Context *c, uint8_t *dst[4], ++ const int stride[4], int comp, ++ const DCIPredict *top_row, int top_by, ++ const MVPredict *top_mv, ++ const DCIPredict *bottom_row, int bottom_by, ++ const MVPredict *bottom_mv, ++ int rel_y, int edge_y, int slice_start) ++{ ++ const int chroma = comp != 0; ++ const int plane = comp == 1 ? 2 : comp == 2 ? 1 : 0; ++ const int blocks_per_mb = chroma ? 2 : 4; ++ const int plane_w = chroma ? (c->avctx->width + 1) >> 1 : c->avctx->width; ++ const int blocks_w = (plane_w + 7) >> 3; ++ ++ if ((c->frame_flags & 0x8000) || edge_y == slice_start || ++ edge_y >= c->avctx->height || ++ bink2g_filter_map_skip(c->row_cbp, edge_y)) ++ return; ++ ++ for (int bx = 0; bx < blocks_w; bx++) { ++ const int mb = bx / blocks_per_mb; ++ const int local_x = bx % blocks_per_mb; ++ const uint16_t top_state = top_row[mb].state[comp] ++ [top_by * blocks_per_mb + local_x]; ++ const uint16_t bottom_state = bottom_row[mb].state[comp] ++ [bottom_by * blocks_per_mb + local_x]; ++ unsigned top = bink2g_filter_strength(top_state); ++ unsigned bottom = bink2g_filter_strength(bottom_state); ++ ++ bink2g_select_coding_edge(top_state, bottom_state, &top, &bottom); ++ ++ if (chroma || bottom_by == 0 || bottom_by == 2) { ++ const int qcol = chroma ? local_x : local_x >> 1; ++ const int top_q = (bottom_by ? 0 : 2) + qcol; ++ const int bottom_q = (bottom_by ? 2 : 0) + qcol; ++ ++ bink2g_force_mv_edge(&top_mv[mb].mv, top_q, ++ &bottom_mv[mb].mv, bottom_q, ++ top_state, bottom_state, &top, &bottom); ++ } ++ ++ bink2g_filter_horizontal_edge(dst[plane] + bx * 8 + ++ rel_y * stride[plane], ++ stride[plane], top, bottom); ++ } ++} ++ ++static void bink2g_postprocess_row(Bink2Context *c, uint8_t *dst[4], ++ const int stride[4], int y, ++ int frame_end, ++ const DCIPredict *prev_idc, ++ const DCIPredict *current_idc, ++ const MVPredict *prev_mv, ++ const MVPredict *current_mv) ++{ ++ const int is_last = y + 32 >= frame_end; ++ ++ bink2g_reconstruct_row(c, dst, stride, y, ++ prev_idc, current_idc, is_last); ++ ++ for (int comp = 0; comp < 3; comp++) { ++ const int chroma = comp != 0; ++ const int blocks_per_mb = chroma ? 2 : 4; ++ ++ if (y > 0) ++ bink2g_filter_vertical_row(c, dst, stride, comp, prev_idc, ++ prev_mv, ++ blocks_per_mb - 1, -8); ++ for (int by = 0; by < blocks_per_mb - 1; by++) ++ bink2g_filter_vertical_row(c, dst, stride, comp, current_idc, ++ current_mv, by, by * 8); ++ if (is_last) ++ bink2g_filter_vertical_row(c, dst, stride, comp, current_idc, ++ current_mv, ++ blocks_per_mb - 1, ++ (blocks_per_mb - 1) * 8); ++ } ++ ++ if (y > 0) { ++ bink2g_filter_horizontal_row(c, dst, stride, 0, ++ prev_idc, 2, prev_mv, ++ prev_idc, 3, prev_mv, ++ -8, y - 8, 0); ++ bink2g_filter_horizontal_row(c, dst, stride, 0, ++ prev_idc, 3, prev_mv, ++ current_idc, 0, current_mv, ++ 0, y, 0); ++ for (int comp = 1; comp <= 2; comp++) { ++ bink2g_filter_horizontal_row(c, dst, stride, comp, ++ prev_idc, 0, prev_mv, ++ prev_idc, 1, prev_mv, ++ -8, y - 16, 0); ++ bink2g_filter_horizontal_row(c, dst, stride, comp, ++ prev_idc, 1, prev_mv, ++ current_idc, 0, current_mv, ++ 0, y, 0); ++ } ++ } ++ ++ bink2g_filter_horizontal_row(c, dst, stride, 0, ++ current_idc, 0, current_mv, ++ current_idc, 1, current_mv, ++ 8, y + 8, 0); ++ bink2g_filter_horizontal_row(c, dst, stride, 0, ++ current_idc, 1, current_mv, ++ current_idc, 2, current_mv, ++ 16, y + 16, 0); ++ ++ if (is_last) { ++ bink2g_filter_horizontal_row(c, dst, stride, 0, ++ current_idc, 2, current_mv, ++ current_idc, 3, current_mv, ++ 24, y + 24, 0); ++ for (int comp = 1; comp <= 2; comp++) ++ bink2g_filter_horizontal_row(c, dst, stride, comp, ++ current_idc, 0, current_mv, ++ current_idc, 1, current_mv, ++ 8, y + 16, 0); ++ } ++} ++ ++static void bink2g_postprocess_frame(Bink2Context *c, uint8_t *dst[4], ++ const int stride[4]) ++{ ++ const int mb_width = (c->avctx->width + 31) >> 5; ++ const int mb_height = (c->avctx->height + 31) >> 5; ++ const int frame_end = mb_height << 5; ++ ++ for (int mb_y = 0; mb_y < mb_height; mb_y++) { ++ const int y = mb_y << 5; ++ const int prev_y = mb_y ? mb_y - 1 : 0; ++ const DCIPredict *prev_idc = c->frame_idc + prev_y * mb_width; ++ const DCIPredict *current_idc = c->frame_idc + mb_y * mb_width; ++ const MVPredict *prev_mv = c->frame_mv + prev_y * mb_width; ++ const MVPredict *current_mv = c->frame_mv + mb_y * mb_width; ++ uint8_t *row_dst[4] = { ++ dst[0] + y * stride[0], ++ dst[1] + (y >> 1) * stride[1], ++ dst[2] + (y >> 1) * stride[2], ++ c->has_alpha ? dst[3] + y * stride[3] : NULL, ++ }; ++ ++ bink2g_postprocess_row(c, row_dst, stride, y, frame_end, ++ prev_idc, current_idc, prev_mv, current_mv); ++ } ++} ++ ++static int bink2g_decode_slice(Bink2Context *c, ++ uint8_t *dst[4], int stride[4], ++ uint8_t *src[4], int sstride[4], ++ int is_kf, int start, int end) ++{ ++ GetBitContext *gb = &c->gb; ++ int w = c->avctx->width; ++ int h = c->avctx->height; ++ const int mb_width = (w + 31) / 32; ++ int ret = 0, dq, flags; ++ ++ memset(c->prev_q, 0, mb_width * sizeof(*c->prev_q)); ++ memset(c->prev_mv, 0, mb_width * sizeof(*c->prev_mv)); ++ memset(c->prev_idc, 0, mb_width * sizeof(*c->prev_idc)); ++ memset(c->current_idc, 0, mb_width * sizeof(*c->current_idc)); ++ ++ for (int y = start; y < end; y += 32) { ++ int types_lru[4] = { MOTION_BLOCK, RESIDUE_BLOCK, SKIP_BLOCK, INTRA_BLOCK }; ++ unsigned y_cbp_intra = 0, u_cbp_intra = 0, v_cbp_intra = 0, a_cbp_intra = 0; ++ unsigned y_cbp_inter = 0, u_cbp_inter = 0, v_cbp_inter = 0, a_cbp_inter = 0; ++ ++ memset(c->current_q, 0, ((c->avctx->width + 31) / 32) * sizeof(*c->current_q)); ++ memset(c->current_mv, 0, ((c->avctx->width + 31) / 32) * sizeof(*c->current_mv)); ++ ++ for (int x = 0; x < c->avctx->width; x += 32) { ++ int type = is_kf ? INTRA_BLOCK : bink2g_get_type(gb, types_lru); ++ int8_t *intra_q = &c->current_q[x / 32].intra_q; ++ int8_t *inter_q = &c->current_q[x / 32].inter_q; ++ MVectors mv = { 0 }; ++ ++ c->mb_pos = x / 32; ++ memset(c->current_idc[c->mb_pos].state, 0, ++ sizeof(c->current_idc[c->mb_pos].state)); ++ c->current_idc[c->mb_pos].block_type = type; ++ flags = 0; ++ if (y == start) ++ flags |= 0x80; ++ if (!x) ++ flags |= 0x20; ++ if (x == 32) ++ flags |= 0x200; ++ if (x + 32 >= c->avctx->width) ++ flags |= 0x40; ++ switch (type) { ++ case INTRA_BLOCK: ++ if (!(flags & 0xA0) && c->prev_idc[c->mb_pos - 1].block_type != INTRA_BLOCK) { ++ bink2g_average_luma (c, x -32, -32, dst[0], stride[0], c->prev_idc[c->mb_pos - 1].dc[0]); ++ bink2g_average_chroma(c, x/2-16, -16, dst[2], stride[2], c->prev_idc[c->mb_pos - 1].dc[1]); ++ bink2g_average_chroma(c, x/2-16, -16, dst[1], stride[1], c->prev_idc[c->mb_pos - 1].dc[2]); ++ if (c->has_alpha) ++ bink2g_average_luma(c, x-32, -32, dst[3], stride[3], c->prev_idc[c->mb_pos - 1].dc[3]); ++ } ++ if (!(flags & 0x20) && c->current_idc[c->mb_pos - 1].block_type != INTRA_BLOCK) { ++ bink2g_average_luma (c, x -32, 0, dst[0], stride[0], c->current_idc[c->mb_pos - 1].dc[0]); ++ bink2g_average_chroma(c, x/2-16, 0, dst[2], stride[2], c->current_idc[c->mb_pos - 1].dc[1]); ++ bink2g_average_chroma(c, x/2-16, 0, dst[1], stride[1], c->current_idc[c->mb_pos - 1].dc[2]); ++ if (c->has_alpha) ++ bink2g_average_luma(c, x-32, 0, dst[3], stride[3], c->current_idc[c->mb_pos - 1].dc[3]); ++ } ++ if ((flags & 0x20) && !(flags & 0x80) && c->prev_idc[c->mb_pos + 1].block_type != INTRA_BLOCK) { ++ bink2g_average_luma (c, x +32, -32, dst[0], stride[0], c->prev_idc[c->mb_pos + 1].dc[0]); ++ bink2g_average_chroma(c, x/2+16, -16, dst[2], stride[2], c->prev_idc[c->mb_pos + 1].dc[1]); ++ bink2g_average_chroma(c, x/2+16, -16, dst[1], stride[1], c->prev_idc[c->mb_pos + 1].dc[2]); ++ if (c->has_alpha) ++ bink2g_average_luma(c, x+32, -32, dst[3], stride[3], c->prev_idc[c->mb_pos + 1].dc[3]); ++ } ++ if (!(flags & 0x80) && c->prev_idc[c->mb_pos].block_type != INTRA_BLOCK) { ++ bink2g_average_luma (c, x, -32, dst[0], stride[0], c->prev_idc[c->mb_pos].dc[0]); ++ bink2g_average_chroma(c, x/2, -16, dst[2], stride[2], c->prev_idc[c->mb_pos].dc[1]); ++ bink2g_average_chroma(c, x/2, -16, dst[1], stride[1], c->prev_idc[c->mb_pos].dc[2]); ++ if (c->has_alpha) ++ bink2g_average_luma(c, x, -32, dst[3], stride[3], c->prev_idc[c->mb_pos].dc[3]); ++ } ++ ++ bink2g_predict_mv(c, x, y, flags, mv); ++ update_inter_q(c, inter_q, 0, flags); ++ dq = bink2g_decode_dq(gb); ++ update_intra_q(c, intra_q, dq, flags); ++ if (*intra_q < 0 || *intra_q >= 37) { ++ ret = AVERROR_INVALIDDATA; ++ goto fail; ++ } ++ c->comp = 0; ++ ret = bink2g_decode_intra_luma(c, gb, c->iblock, &y_cbp_intra, *intra_q, &c->dsp, ++ dst[0] + x, stride[0], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 1; ++ ret = bink2g_decode_intra_chroma(c, gb, c->iblock, &u_cbp_intra, *intra_q, &c->dsp, ++ dst[2] + x/2, stride[2], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2g_decode_intra_chroma(c, gb, c->iblock, &v_cbp_intra, *intra_q, &c->dsp, ++ dst[1] + x/2, stride[1], flags); ++ if (ret < 0) ++ goto fail; ++ if (c->has_alpha) { ++ c->comp = 3; ++ ret = bink2g_decode_intra_luma(c, gb, c->iblock, &a_cbp_intra, *intra_q, &c->dsp, ++ dst[3] + x, stride[3], flags); ++ if (ret < 0) ++ goto fail; ++ } ++ break; ++ case SKIP_BLOCK: ++ update_inter_q(c, inter_q, 0, flags); ++ update_intra_q(c, intra_q, 0, flags); ++ copy_block16(dst[0] + x, src[0] + x + sstride[0] * y, ++ stride[0], sstride[0], 32); ++ copy_block16(dst[0] + x + 16, src[0] + x + 16 + sstride[0] * y, ++ stride[0], sstride[0], 32); ++ copy_block16(dst[1] + (x/2), src[1] + (x/2) + sstride[1] * (y/2), ++ stride[1], sstride[1], 16); ++ copy_block16(dst[2] + (x/2), src[2] + (x/2) + sstride[2] * (y/2), ++ stride[2], sstride[2], 16); ++ if (c->has_alpha) { ++ copy_block16(dst[3] + x, src[3] + x + sstride[3] * y, ++ stride[3], sstride[3], 32); ++ copy_block16(dst[3] + x + 16, src[3] + x + 16 + sstride[3] * y, ++ stride[3], sstride[3], 32); ++ } ++ break; ++ case MOTION_BLOCK: ++ update_intra_q(c, intra_q, 0, flags); ++ update_inter_q(c, inter_q, 0, flags); ++ ret = bink2g_decode_mv(c, gb, x, y, &mv); ++ if (ret < 0) ++ goto fail; ++ bink2g_predict_mv(c, x, y, flags, mv); ++ c->comp = 0; ++ ret = bink2g_mcompensate_luma(c, x, y, ++ dst[0], stride[0], ++ src[0], sstride[0], ++ w, h); ++ if (ret < 0) ++ goto fail; ++ c->comp = 1; ++ ret = bink2g_mcompensate_chroma(c, x/2, y/2, ++ dst[2], stride[2], ++ src[2], sstride[2], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2g_mcompensate_chroma(c, x/2, y/2, ++ dst[1], stride[1], ++ src[1], sstride[1], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ if (c->has_alpha) { ++ c->comp = 3; ++ ret = bink2g_mcompensate_luma(c, x, y, ++ dst[3], stride[3], ++ src[3], sstride[3], ++ w, h); ++ if (ret < 0) ++ goto fail; ++ } ++ break; ++ case RESIDUE_BLOCK: ++ update_intra_q(c, intra_q, 0, flags); ++ ret = bink2g_decode_mv(c, gb, x, y, &mv); ++ if (ret < 0) ++ goto fail; ++ bink2g_predict_mv(c, x, y, flags, mv); ++ dq = bink2g_decode_dq(gb); ++ update_inter_q(c, inter_q, dq, flags); ++ if (*inter_q < 0 || *inter_q >= 37) { ++ ret = AVERROR_INVALIDDATA; ++ goto fail; ++ } ++ c->comp = 0; ++ ret = bink2g_mcompensate_luma(c, x, y, ++ dst[0], stride[0], ++ src[0], sstride[0], ++ w, h); ++ if (ret < 0) ++ goto fail; ++ c->comp = 1; ++ ret = bink2g_mcompensate_chroma(c, x/2, y/2, ++ dst[2], stride[2], ++ src[2], sstride[2], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2g_mcompensate_chroma(c, x/2, y/2, ++ dst[1], stride[1], ++ src[1], sstride[1], ++ w/2, h/2); ++ if (ret < 0) ++ goto fail; ++ if (c->has_alpha) { ++ c->comp = 3; ++ ret = bink2g_mcompensate_luma(c, x, y, ++ dst[3], stride[3], ++ src[3], sstride[3], ++ w, h); ++ if (ret < 0) ++ goto fail; ++ } ++ c->comp = 0; ++ ret = bink2g_decode_inter_luma(c, gb, c->iblock, &y_cbp_inter, *inter_q, &c->dsp, ++ dst[0] + x, stride[0], flags); ++ if (ret < 0) ++ goto fail; ++ if (get_bits1(gb)) { ++ c->comp = 1; ++ ret = bink2g_decode_inter_chroma(c, gb, c->iblock, &u_cbp_inter, *inter_q, &c->dsp, ++ dst[2] + x/2, stride[2], flags); ++ if (ret < 0) ++ goto fail; ++ c->comp = 2; ++ ret = bink2g_decode_inter_chroma(c, gb, c->iblock, &v_cbp_inter, *inter_q, &c->dsp, ++ dst[1] + x/2, stride[1], flags); ++ if (ret < 0) ++ goto fail; ++ } else { ++ u_cbp_inter = 0; ++ v_cbp_inter = 0; ++ } ++ if (c->has_alpha) { ++ c->comp = 3; ++ ret = bink2g_decode_inter_luma(c, gb, c->iblock, &a_cbp_inter, *inter_q, &c->dsp, ++ dst[3] + x, stride[3], flags); ++ if (ret < 0) ++ goto fail; ++ } ++ break; ++ default: ++ return AVERROR_INVALIDDATA; ++ } ++ ++ } ++ ++ memcpy(c->frame_idc + (y >> 5) * mb_width, c->current_idc, ++ mb_width * sizeof(*c->frame_idc)); ++ memcpy(c->frame_mv + (y >> 5) * mb_width, c->current_mv, ++ mb_width * sizeof(*c->frame_mv)); ++ ++ dst[0] += stride[0] * 32; ++ dst[1] += stride[1] * 16; ++ dst[2] += stride[2] * 16; ++ if (c->has_alpha) ++ dst[3] += stride[3] * 32; ++ ++ FFSWAP(MVPredict *, c->current_mv, c->prev_mv); ++ FFSWAP(QuantPredict *, c->current_q, c->prev_q); ++ FFSWAP(DCIPredict *, c->current_idc, c->prev_idc); ++ } ++fail: ++ emms_c(); ++ ++ return ret; ++} ++ ++#endif /* AVCODEC_BINK2G_H */ +diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c +index d58c3d889c..92c27e58d4 100644 +--- a/libavcodec/codec_desc.c ++++ b/libavcodec/codec_desc.c +@@ -1959,6 +1959,13 @@ static const AVCodecDescriptor codec_descriptors[] = { + .long_name = NULL_IF_CONFIG_SMALL("LEAD MCMP"), + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY, + }, ++ { ++ .id = AV_CODEC_ID_BINKVIDEO2, ++ .type = AVMEDIA_TYPE_VIDEO, ++ .name = "binkvideo2", ++ .long_name = NULL_IF_CONFIG_SMALL("Bink video 2"), ++ .props = AV_CODEC_PROP_LOSSY, ++ }, + + /* various PCM "codecs" */ + { +diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h +index 0a8d3bed1e..a7e874c890 100644 +--- a/libavcodec/codec_id.h ++++ b/libavcodec/codec_id.h +@@ -322,6 +322,7 @@ enum AVCodecID { + AV_CODEC_ID_RTV1, + AV_CODEC_ID_VMIX, + AV_CODEC_ID_LEAD, ++ AV_CODEC_ID_BINKVIDEO2, + + /* various PCM "codecs" */ + AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs +diff --git a/libavformat/bink.c b/libavformat/bink.c +index 0632d390a2..fbb0238795 100644 +--- a/libavformat/bink.c ++++ b/libavformat/bink.c +@@ -72,7 +72,7 @@ static int probe(const AVProbeData *p) + b[3] == 'k')) || + (b[0] == 'K' && b[1] == 'B' && b[2] == '2' && /* Bink 2 */ + (b[3] == 'a' || b[3] == 'd' || b[3] == 'f' || b[3] == 'g' || b[3] == 'h' || +- b[3] == 'i' || b[3] == 'j' || b[3] == 'k'))) && ++ b[3] == 'i' || b[3] == 'j' || b[3] == 'k' || b[3] == 'm' || b[3] == 'n'))) && + AV_RL32(b+8) > 0 && // num_frames + AV_RL32(b+20) > 0 && AV_RL32(b+20) <= BINK_MAX_WIDTH && + AV_RL32(b+24) > 0 && AV_RL32(b+24) <= BINK_MAX_HEIGHT && +@@ -149,8 +149,7 @@ static int read_header(AVFormatContext *s) + vst->codecpar->codec_id = AV_CODEC_ID_BINKVIDEO; + + if ((vst->codecpar->codec_tag & 0xFFFFFF) == MKTAG('K', 'B', '2', 0)) { +- av_log(s, AV_LOG_WARNING, "Bink 2 video is not implemented\n"); +- vst->codecpar->codec_id = AV_CODEC_ID_NONE; ++ vst->codecpar->codec_id = AV_CODEC_ID_BINKVIDEO2; + } + + if ((ret = ff_get_extradata(s, vst->codecpar, pb, 4)) < 0) +@@ -168,8 +167,11 @@ static int read_header(AVFormatContext *s) + signature = (vst->codecpar->codec_tag & 0xFFFFFF); + revision = ((vst->codecpar->codec_tag >> 24) % 0xFF); + +- if ((signature == AV_RL32("BIK") && (revision == 'k')) || +- (signature == AV_RL32("KB2") && (revision == 'i' || revision == 'j' || revision == 'k'))) ++ if (signature == AV_RL32("KB2") && revision == 'm') ++ avio_skip(pb, 16); /* opaque KB2m header extension */ ++ else if ((signature == AV_RL32("BIK") && revision == 'k') || ++ (signature == AV_RL32("KB2") && ++ (revision == 'i' || revision == 'j' || revision == 'k' || revision == 'n'))) + avio_skip(pb, 4); /* unknown new field */ + + if (bink->num_audio_tracks) { diff --git a/portfiles/ffmpeg/1001-bink2-alpha-boolean.patch b/portfiles/ffmpeg/1001-bink2-alpha-boolean.patch new file mode 100644 index 000000000..8253b803e --- /dev/null +++ b/portfiles/ffmpeg/1001-bink2-alpha-boolean.patch @@ -0,0 +1,11 @@ +--- a/libavcodec/bink2.c ++++ b/libavcodec/bink2.c +@@ -359,7 +359,7 @@ + } + c->flags = AV_RL32(avctx->extradata); + av_log(avctx, AV_LOG_DEBUG, "flags: 0x%X\n", c->flags); +- c->has_alpha = c->flags & BINK_FLAG_ALPHA; ++ c->has_alpha = !!(c->flags & BINK_FLAG_ALPHA); + c->avctx = avctx; + c->slice_ctx[0] = c; + diff --git a/portfiles/ffmpeg/FindFFMPEG.cmake.in b/portfiles/ffmpeg/FindFFMPEG.cmake.in new file mode 100644 index 000000000..7461a3cec --- /dev/null +++ b/portfiles/ffmpeg/FindFFMPEG.cmake.in @@ -0,0 +1,166 @@ +# Distributed under the OSI-approved BSD 3-Clause License. +# +#.rst: +# FindFFMPEG +# -------- +# +# Find the FFPMEG libraries +# +# Result Variables +# ^^^^^^^^^^^^^^^^ +# +# The following variables will be defined: +# +# ``FFMPEG_FOUND`` +# True if FFMPEG found on the local system +# +# ``FFMPEG_INCLUDE_DIRS`` +# Location of FFMPEG header files +# +# ``FFMPEG_LIBRARY_DIRS`` +# Location of FFMPEG libraries +# +# ``FFMPEG_LIBRARIES`` +# List of the FFMPEG libraries found +# +# + +include(FindPackageHandleStandardArgs) +include(SelectLibraryConfigurations) +include(CMakeFindDependencyMacro) + +if(NOT FFMPEG_FOUND) + +# Compute the installation path relative to this file. +get_filename_component(SEARCH_PATH "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(SEARCH_PATH "${SEARCH_PATH}" PATH) +get_filename_component(SEARCH_PATH "${SEARCH_PATH}" PATH) +if(SEARCH_PATH STREQUAL "/") + set(SEARCH_PATH "") +endif() + +set(FFMPEG_VERSION "@FFMPEG_VERSION@") + +function(append_dependencies out) + cmake_parse_arguments(PARSE_ARGV 1 "arg" "DEBUG" "NAMES" "") + if(${arg_DEBUG}) + set(config DEBUG) + set(path "${SEARCH_PATH}/debug/lib/") + else() + set(config RELEASE) + set(path "${SEARCH_PATH}/lib/") + endif() + if("${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}" STREQUAL "") + enable_language(CXX) + endif() + set(pass_through + ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES} + advapi32 bcrypt crypt32 gdi32 mfuuid ole32 oleaut32 psapi secur32 shlwapi strmiids user32 uuid vfw32 ws2_32 usp10 cfgmgr32 rpcrt4 + -pthread -pthreads pthread atomic m + ) + cmake_policy(SET CMP0057 NEW) + foreach(lib_name IN LISTS arg_NAMES) + if(lib_name IN_LIST CMAKE_C_IMPLICIT_LINK_LIBRARIES) + continue() # implicit even for C + elseif(lib_name IN_LIST pass_through) + list(APPEND ${out} "${lib_name}") + elseif(EXISTS "${lib_name}") + list(APPEND ${out} "${lib_name}") + else() + # first look in ${path} specifically to ensure we find the right release/debug variant + find_library(FFMPEG_DEPENDENCY_${lib_name}_${config} NAMES "${lib_name}" PATHS "${path}" NO_DEFAULT_PATH) + # if not found there, must be a system dependency, so look elsewhere + find_library(FFMPEG_DEPENDENCY_${lib_name}_${config} NAMES "${lib_name}" REQUIRED) + list(APPEND ${out} "${FFMPEG_DEPENDENCY_${lib_name}_${config}}") + endif() + endforeach() + set("${out}" "${${out}}" PARENT_SCOPE) +endfunction() + +macro(FFMPEG_FIND varname shortname headername) + if(NOT FFMPEG_${varname}_INCLUDE_DIRS) + find_path(FFMPEG_${varname}_INCLUDE_DIRS NAMES lib${shortname}/${headername} ${headername} PATHS ${SEARCH_PATH}/include NO_DEFAULT_PATH) + endif() + if(NOT FFMPEG_${varname}_LIBRARY) + find_library(FFMPEG_${varname}_LIBRARY_RELEASE NAMES ${shortname} PATHS ${SEARCH_PATH}/lib/ NO_DEFAULT_PATH) + find_library(FFMPEG_${varname}_LIBRARY_DEBUG NAMES ${shortname}d ${shortname} PATHS ${SEARCH_PATH}/debug/lib/ NO_DEFAULT_PATH) + get_filename_component(FFMPEG_${varname}_LIBRARY_RELEASE_DIR ${FFMPEG_${varname}_LIBRARY_RELEASE} DIRECTORY) + get_filename_component(FFMPEG_${varname}_LIBRARY_DEBUG_DIR ${FFMPEG_${varname}_LIBRARY_DEBUG} DIRECTORY) + select_library_configurations(FFMPEG_${varname}) + set(FFMPEG_${varname}_LIBRARY ${FFMPEG_${varname}_LIBRARY} CACHE STRING "") + endif() + if (FFMPEG_${varname}_LIBRARY AND FFMPEG_${varname}_INCLUDE_DIRS) + set(FFMPEG_${varname}_FOUND TRUE BOOL) + list(APPEND FFMPEG_INCLUDE_DIRS ${FFMPEG_${varname}_INCLUDE_DIRS}) + list(APPEND FFMPEG_LIBRARIES ${FFMPEG_${varname}_LIBRARY}) + list(APPEND FFMPEG_LIBRARY_DIRS ${FFMPEG_${varname}_LIBRARY_RELEASE_DIR} ${FFMPEG_${varname}_LIBRARY_DEBUG_DIR}) + endif() +endmacro(FFMPEG_FIND) + +if(@ENABLE_AVDEVICE@) + FFMPEG_FIND(libavdevice avdevice avdevice.h) +endif() +if(@ENABLE_AVFILTER@) + FFMPEG_FIND(libavfilter avfilter avfilter.h) +endif() +if(@ENABLE_AVFORMAT@) + FFMPEG_FIND(libavformat avformat avformat.h) +endif() +if(@ENABLE_AVCODEC@) + FFMPEG_FIND(libavcodec avcodec avcodec.h) +endif() +if(@ENABLE_POSTPROC@) + FFMPEG_FIND(libpostproc postproc postprocess.h) +endif() +if(@ENABLE_SWRESAMPLE@) + FFMPEG_FIND(libswresample swresample swresample.h) +endif() +if(@ENABLE_SWSCALE@) + FFMPEG_FIND(libswscale swscale swscale.h) +endif() +FFMPEG_FIND(libavutil avutil avutil.h) + +if (FFMPEG_libavutil_FOUND) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_LIBRARY_DIRS) + set(FFMPEG_libavutil_VERSION "@LIBAVUTIL_VERSION@" CACHE STRING "") + + if(FFMPEG_libavcodec_FOUND) + set(FFMPEG_libavcodec_VERSION "@LIBAVCODEC_VERSION@" CACHE STRING "") + endif() + if(FFMPEG_libavdevice_FOUND) + set(FFMPEG_libavdevice_VERSION "@LIBAVDEVICE_VERSION@" CACHE STRING "") + endif() + if(FFMPEG_libavfilter_FOUND) + set(FFMPEG_libavfilter_VERSION "@LIBAVFILTER_VERSION@" CACHE STRING "") + endif() + if(FFMPEG_libavformat_FOUND) + set(FFMPEG_libavformat_VERSION "@LIBAVFORMAT_VERSION@" CACHE STRING "") + endif() + if(FFMPEG_libswresample_FOUND) + set(FFMPEG_libswresample_VERSION "@LIBSWRESAMPLE_VERSION@" CACHE STRING "") + endif() + if(FFMPEG_libswscale_FOUND) + set(FFMPEG_libswscale_VERSION "@LIBSWSCALE_VERSION@" CACHE STRING "") + endif() + + append_dependencies(FFMPEG_DEPS_LIBRARY_RELEASE NAMES "@FFMPEG_DEPENDENCIES_RELEASE@") + append_dependencies(FFMPEG_DEPS_LIBRARY_DEBUG NAMES "@FFMPEG_DEPENDENCIES_DEBUG@" DEBUG) + if(FFMPEG_DEPS_LIBRARY_RELEASE OR FFMPEG_DEPS_LIBRARY_DEBUG) + select_library_configurations(FFMPEG_DEPS) + list(APPEND FFMPEG_LIBRARIES ${FFMPEG_DEPS_LIBRARY}) + endif() + + set(FFMPEG_LIBRARY ${FFMPEG_LIBRARIES}) + + set(FFMPEG_FOUND TRUE CACHE BOOL "") + set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "") + set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "") + set(FFMPEG_LIBRARY_DIRS ${FFMPEG_LIBRARY_DIRS} CACHE STRING "") +endif() + +find_package_handle_standard_args(FFMPEG REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_LIBRARY_DIRS FFMPEG_INCLUDE_DIRS) + +set(z_vcpkg_using_vcpkg_find_ffmpeg ${FFMPEG_FOUND}) + +endif() diff --git a/portfiles/ffmpeg/build.sh.in b/portfiles/ffmpeg/build.sh.in new file mode 100644 index 000000000..fd794d41c --- /dev/null +++ b/portfiles/ffmpeg/build.sh.in @@ -0,0 +1,142 @@ +#!/usr/bin/env bash + +set -e + +move_binary() { + SOURCE=$1 + TARGET=$2 + BINARY=$3 + + # run lipo over the command to check whether it really + # is a binary that we need to merge architectures + lipo $SOURCE/$BINARY -info &> /dev/null || return 0 + + # get the directory name the file is in + DIRNAME=$(dirname $BINARY) + + # ensure the directory to move the binary to exists + mkdir -p $TARGET/$DIRNAME + + # now finally move the binary + mv $SOURCE/$BINARY $TARGET/$BINARY +} + +move_binaries() { + SOURCE=$1 + TARGET=$2 + + [ ! -d $SOURCE ] && return 0 + pushd $SOURCE + + for BINARY in $(find . -type f); do + move_binary $SOURCE $TARGET $BINARY + done + + popd +} + +merge_binaries() { + TARGET=$1 + SOURCE=$2 + + shift + shift + + pushd $SOURCE/$1 + BINARIES=$(find . -type f) + popd + + for BINARY in $BINARIES; do + COMMAND="lipo -create -output $TARGET/$BINARY" + + for ARCH in $@; do + COMMAND="$COMMAND -arch $ARCH $SOURCE/$ARCH/$BINARY" + done + + $($COMMAND) + done +} + +PATH_TO_BUILD_DIR="@BUILD_DIR@" +PATH_TO_SRC_DIR="@SOURCE_PATH@" +PATH_TO_PACKAGE_DIR="@INST_PREFIX@" + +JOBS=@VCPKG_CONCURRENCY@ + +OSX_ARCHS="@OSX_ARCHS@" +OSX_ARCH_COUNT=0@OSX_ARCH_COUNT@ + +# Default to hardware concurrency if unset. +: ${JOBS:=$(nproc)} + +# Disable asm and x86asm on all android targets because they trigger build failures: +# arm64 Android build fails with 'relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol ff_cos_32; recompile with -fPIC' +# x86 Android build fails with 'error: inline assembly requires more registers than available'. +# x64 Android build fails with 'relocation R_X86_64_PC32 cannot be used against symbol ff_h264_cabac_tables; recompile with -fPIC' +if [ "@VCPKG_CMAKE_SYSTEM_NAME@" = "Android" ]; then + OPTIONS_arm=" --disable-asm --disable-x86asm" + OPTIONS_arm64=" --disable-asm --disable-x86asm" + OPTIONS_x86=" --disable-asm --disable-x86asm" + OPTIONS_x86_64="${OPTIONS_x86}" +else + OPTIONS_arm=" --disable-asm --disable-x86asm" + OPTIONS_arm64=" --enable-asm --disable-x86asm" + OPTIONS_x86=" --enable-asm --enable-x86asm" + OPTIONS_x86_64="${OPTIONS_x86}" +fi + +case "@VCPKG_CMAKE_SYSTEM_NAME@" in + *BSD) + MAKE_BINARY="gmake" + ;; + *) + MAKE_BINARY="make" + ;; +esac + +build_ffmpeg() { + # extract build architecture + BUILD_ARCH=$1 + shift + + echo "BUILD_ARCH=${BUILD_ARCH}" + + # get architecture-specific options + OPTION_VARIABLE="OPTIONS_${BUILD_ARCH}" + echo "OPTION_VARIABLE=${OPTION_VARIABLE}" + + echo "=== CONFIGURING ===" + + sh "$PATH_TO_SRC_DIR/configure" "--prefix=$PATH_TO_PACKAGE_DIR" @CONFIGURE_OPTIONS@ --arch=${BUILD_ARCH} ${!OPTION_VARIABLE} $@ + + echo "=== BUILDING ===" + + $MAKE_BINARY -j${JOBS} V=1 + + echo "=== INSTALLING ===" + + $MAKE_BINARY install +} + +cd "$PATH_TO_BUILD_DIR" + +if [ $OSX_ARCH_COUNT -gt 0 ]; then + for ARCH in $OSX_ARCHS; do + echo "=== CLEANING FOR $ARCH ===" + + $MAKE_BINARY clean && $MAKE_BINARY distclean + + build_ffmpeg $ARCH --extra-cflags=-arch --extra-cflags=$ARCH --extra-ldflags=-arch --extra-ldflags=$ARCH + + echo "=== COLLECTING BINARIES FOR $ARCH ===" + + move_binaries $PATH_TO_PACKAGE_DIR/lib $PATH_TO_BUILD_DIR/stage/$ARCH/lib + move_binaries $PATH_TO_PACKAGE_DIR/bin $PATH_TO_BUILD_DIR/stage/$ARCH/bin + done + + echo "=== MERGING ARCHITECTURES ===" + + merge_binaries $PATH_TO_PACKAGE_DIR $PATH_TO_BUILD_DIR/stage $OSX_ARCHS +else + build_ffmpeg @BUILD_ARCH@ +fi diff --git a/portfiles/ffmpeg/portfile.cmake b/portfiles/ffmpeg/portfile.cmake new file mode 100644 index 000000000..4483e69b8 --- /dev/null +++ b/portfiles/ffmpeg/portfile.cmake @@ -0,0 +1,1040 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO ffmpeg/ffmpeg + REF "n${VERSION}" + SHA512 8411c45f71d2d61184b11e2a786137044a80d9b979a7e2e8513efc5e716b3360bff4533a13875dd4bca492b97b97f0384f7fb4f3d796802e81981b0857d18a2b + HEAD_REF master + PATCHES + 0001-create-lib-libraries.patch + 0002-fix-msvc-link.patch + 0003-fix-windowsinclude.patch + 0004-dependencies.patch + 0005-fix-nasm.patch + 0007-fix-lib-naming.patch + 0013-define-WINVER.patch + 0020-fix-aarch64-libswscale.patch + 0024-fix-osx-host-c11.patch + 0040-ffmpeg-add-av_stream_get_first_dts-for-chromium.patch # Do not remove this patch. It is required by chromium + 0041-add-const-for-opengl-definition.patch + 0043-fix-miss-head.patch + 0044-fix-vulkan-debug-callback-abi.patch + # Bink 2 video (Steam MAGES ports): decoder from sharpemu/ffmpeg-core 3b502d4 (LGPL-2.1-or-later) + # and a fix that normalises the alpha flag to a boolean. + 1000-bink2-decoder.patch + 1001-bink2-alpha-boolean.patch +) + +if(SOURCE_PATH MATCHES " ") + message(FATAL_ERROR "Error: ffmpeg will not build with spaces in the path. Please use a directory with no spaces") +endif() + +if (VCPKG_TARGET_ARCHITECTURE STREQUAL "x86" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + vcpkg_find_acquire_program(NASM) + get_filename_component(NASM_EXE_PATH "${NASM}" DIRECTORY) + vcpkg_add_to_path("${NASM_EXE_PATH}") +endif() + +set(OPTIONS "--enable-pic --disable-doc --enable-debug --enable-runtime-cpudetect --disable-autodetect") + +if(VCPKG_TARGET_IS_MINGW) + if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86") + string(APPEND OPTIONS " --target-os=mingw32") + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + string(APPEND OPTIONS " --target-os=mingw64") + endif() +elseif(VCPKG_TARGET_IS_LINUX) + string(APPEND OPTIONS " --target-os=linux --enable-pthreads") +elseif(VCPKG_TARGET_IS_UWP) + string(APPEND OPTIONS " --target-os=win32 --enable-w32threads --enable-d3d11va --enable-d3d12va --enable-mediafoundation") +elseif(VCPKG_TARGET_IS_WINDOWS) + string(APPEND OPTIONS " --target-os=win32 --enable-w32threads --enable-d3d11va --enable-d3d12va --enable-dxva2 --enable-mediafoundation") +elseif(VCPKG_TARGET_IS_OSX) + string(APPEND OPTIONS " --target-os=darwin --enable-appkit --enable-avfoundation --enable-coreimage --enable-audiotoolbox --enable-videotoolbox") +elseif(VCPKG_TARGET_IS_IOS) + string(APPEND OPTIONS " --enable-avfoundation --enable-coreimage --enable-videotoolbox") +elseif(VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Android") + string(APPEND OPTIONS " --target-os=android --enable-jni --enable-mediacodec") +elseif(VCPKG_CMAKE_SYSTEM_NAME STREQUAL "QNX") + string(APPEND OPTIONS " --target-os=qnx") +endif() + +if(VCPKG_TARGET_IS_OSX) + list(JOIN VCPKG_OSX_ARCHITECTURES " " OSX_ARCHS) + list(LENGTH VCPKG_OSX_ARCHITECTURES OSX_ARCH_COUNT) +endif() + +vcpkg_cmake_get_vars(cmake_vars_file) +include("${cmake_vars_file}") +if(VCPKG_DETECTED_MSVC) + string(APPEND OPTIONS " --disable-inline-asm") # clang-cl has inline assembly but this leads to undefined symbols. + set(OPTIONS "--toolchain=msvc ${OPTIONS}") + # This is required because ffmpeg depends upon optimizations to link correctly + string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -O2") + string(REGEX REPLACE "(^| )-RTC1( |$)" " " VCPKG_COMBINED_C_FLAGS_DEBUG "${VCPKG_COMBINED_C_FLAGS_DEBUG}") + string(REGEX REPLACE "(^| )-Od( |$)" " " VCPKG_COMBINED_C_FLAGS_DEBUG "${VCPKG_COMBINED_C_FLAGS_DEBUG}") + string(REGEX REPLACE "(^| )-Ob0( |$)" " " VCPKG_COMBINED_C_FLAGS_DEBUG "${VCPKG_COMBINED_C_FLAGS_DEBUG}") +endif() + +string(APPEND VCPKG_COMBINED_C_FLAGS_DEBUG " -I \"${CURRENT_INSTALLED_DIR}/include\"") +string(APPEND VCPKG_COMBINED_C_FLAGS_RELEASE " -I \"${CURRENT_INSTALLED_DIR}/include\"") + +## Setup vcpkg toolchain + +set(prog_env "") + +if(VCPKG_DETECTED_CMAKE_C_COMPILER) + get_filename_component(CC_path "${VCPKG_DETECTED_CMAKE_C_COMPILER}" DIRECTORY) + get_filename_component(CC_filename "${VCPKG_DETECTED_CMAKE_C_COMPILER}" NAME) + set(ENV{CC} "${CC_filename}") + string(APPEND OPTIONS " --cc=${CC_filename}") + string(APPEND OPTIONS " --host_cc=${CC_filename}") + list(APPEND prog_env "${CC_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_CXX_COMPILER) + get_filename_component(CXX_path "${VCPKG_DETECTED_CMAKE_CXX_COMPILER}" DIRECTORY) + get_filename_component(CXX_filename "${VCPKG_DETECTED_CMAKE_CXX_COMPILER}" NAME) + set(ENV{CXX} "${CXX_filename}") + string(APPEND OPTIONS " --cxx=${CXX_filename}") + #string(APPEND OPTIONS " --host_cxx=${CC_filename}") + list(APPEND prog_env "${CXX_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_RC_COMPILER) + get_filename_component(RC_path "${VCPKG_DETECTED_CMAKE_RC_COMPILER}" DIRECTORY) + get_filename_component(RC_filename "${VCPKG_DETECTED_CMAKE_RC_COMPILER}" NAME) + set(ENV{WINDRES} "${RC_filename}") + string(APPEND OPTIONS " --windres=${RC_filename}") + list(APPEND prog_env "${RC_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_LINKER AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + get_filename_component(LD_path "${VCPKG_DETECTED_CMAKE_LINKER}" DIRECTORY) + get_filename_component(LD_filename "${VCPKG_DETECTED_CMAKE_LINKER}" NAME) + set(ENV{LD} "${LD_filename}") + string(APPEND OPTIONS " --ld=${LD_filename}") + #string(APPEND OPTIONS " --host_ld=${LD_filename}") + list(APPEND prog_env "${LD_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_NM) + get_filename_component(NM_path "${VCPKG_DETECTED_CMAKE_NM}" DIRECTORY) + get_filename_component(NM_filename "${VCPKG_DETECTED_CMAKE_NM}" NAME) + set(ENV{NM} "${NM_filename}") + string(APPEND OPTIONS " --nm=${NM_filename}") + list(APPEND prog_env "${NM_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_AR) + get_filename_component(AR_path "${VCPKG_DETECTED_CMAKE_AR}" DIRECTORY) + get_filename_component(AR_filename "${VCPKG_DETECTED_CMAKE_AR}" NAME) + if(AR_filename MATCHES [[^(llvm-)?lib\.exe$]]) + set(ENV{AR} "ar-lib ${AR_filename}") + string(APPEND OPTIONS " --ar='ar-lib ${AR_filename}'") + else() + set(ENV{AR} "${AR_filename}") + string(APPEND OPTIONS " --ar='${AR_filename}'") + endif() + list(APPEND prog_env "${AR_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_RANLIB) + get_filename_component(RANLIB_path "${VCPKG_DETECTED_CMAKE_RANLIB}" DIRECTORY) + get_filename_component(RANLIB_filename "${VCPKG_DETECTED_CMAKE_RANLIB}" NAME) + set(ENV{RANLIB} "${RANLIB_filename}") + string(APPEND OPTIONS " --ranlib=${RANLIB_filename}") + list(APPEND prog_env "${RANLIB_path}") +endif() + +if(VCPKG_DETECTED_CMAKE_STRIP) + get_filename_component(STRIP_path "${VCPKG_DETECTED_CMAKE_STRIP}" DIRECTORY) + get_filename_component(STRIP_filename "${VCPKG_DETECTED_CMAKE_STRIP}" NAME) + set(ENV{STRIP} "${STRIP_filename}") + string(APPEND OPTIONS " --strip=${STRIP_filename}") + list(APPEND prog_env "${STRIP_path}") +endif() + +if(VCPKG_HOST_IS_WINDOWS) + vcpkg_acquire_msys(MSYS_ROOT PACKAGES automake) + set(SHELL "${MSYS_ROOT}/usr/bin/bash.exe") + vcpkg_execute_required_process( + COMMAND "${SHELL}" -c "'/usr/bin/automake' --print-lib" + OUTPUT_VARIABLE automake_lib + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY "${MSYS_ROOT}" + LOGNAME automake-print-lib + ) + list(APPEND prog_env "${MSYS_ROOT}/usr/bin" "${MSYS_ROOT}${automake_lib}") +else() + find_program(SHELL bash) +endif() + +list(REMOVE_DUPLICATES prog_env) +vcpkg_add_to_path(PREPEND ${prog_env}) + +# More? OBJCC BIN2C + +file(REMOVE_RECURSE "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg" "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel") + +set(FFMPEG_PKGCONFIG_MODULES libavutil) + +if("nonfree" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-nonfree") +endif() + +if("gpl" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-gpl") +endif() + +if("version3" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-version3") +endif() + +if("ffmpeg" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-ffmpeg") +else() + set(OPTIONS "${OPTIONS} --disable-ffmpeg") +endif() + +if("ffplay" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-ffplay") +else() + set(OPTIONS "${OPTIONS} --disable-ffplay") +endif() + +if("ffprobe" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-ffprobe") +else() + set(OPTIONS "${OPTIONS} --disable-ffprobe") +endif() + +if("avcodec" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-avcodec") + set(ENABLE_AVCODEC ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libavcodec) +else() + set(OPTIONS "${OPTIONS} --disable-avcodec") + set(ENABLE_AVCODEC OFF) +endif() + +if("avdevice" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-avdevice") + set(ENABLE_AVDEVICE ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libavdevice) +else() + set(OPTIONS "${OPTIONS} --disable-avdevice") + set(ENABLE_AVDEVICE OFF) +endif() + +if("avformat" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-avformat") + set(ENABLE_AVFORMAT ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libavformat) +else() + set(OPTIONS "${OPTIONS} --disable-avformat") + set(ENABLE_AVFORMAT OFF) +endif() + +if("avfilter" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-avfilter") + set(ENABLE_AVFILTER ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libavfilter) +else() + set(OPTIONS "${OPTIONS} --disable-avfilter") + set(ENABLE_AVFILTER OFF) +endif() + +if("postproc" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-postproc") + set(ENABLE_POSTPROC ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libpostproc) +else() + set(OPTIONS "${OPTIONS} --disable-postproc") + set(ENABLE_POSTPROC OFF) +endif() + +if("swresample" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-swresample") + set(ENABLE_SWRESAMPLE ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libswresample) +else() + set(OPTIONS "${OPTIONS} --disable-swresample") + set(ENABLE_SWRESAMPLE OFF) +endif() + +if("swscale" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-swscale") + set(ENABLE_SWSCALE ON) + list(APPEND FFMPEG_PKGCONFIG_MODULES libswscale) +else() + set(OPTIONS "${OPTIONS} --disable-swscale") + set(ENABLE_SWSCALE OFF) +endif() + +if ("alsa" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-alsa") +else() + set(OPTIONS "${OPTIONS} --disable-alsa") +endif() + +if("amf" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-amf") +else() + set(OPTIONS "${OPTIONS} --disable-amf") +endif() + +if("aom" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libaom") + set(WITH_AOM ON) +else() + set(OPTIONS "${OPTIONS} --disable-libaom") + set(WITH_AOM OFF) +endif() + +if("ass" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libass") + set(WITH_ASS ON) +else() + set(OPTIONS "${OPTIONS} --disable-libass") + set(WITH_ASS OFF) +endif() + +if("avisynthplus" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-avisynth") +else() + set(OPTIONS "${OPTIONS} --disable-avisynth") +endif() + +if("bzip2" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-bzlib") +else() + set(OPTIONS "${OPTIONS} --disable-bzlib") +endif() + +if("dav1d" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libdav1d") + set(WITH_DAV1D ON) +else() + set(OPTIONS "${OPTIONS} --disable-libdav1d") + set(WITH_DAV1D OFF) +endif() + +if("fdk-aac" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libfdk-aac") + set(WITH_AAC ON) +else() + set(OPTIONS "${OPTIONS} --disable-libfdk-aac") + set(WITH_AAC OFF) +endif() + +if("fontconfig" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libfontconfig") +else() + set(OPTIONS "${OPTIONS} --disable-libfontconfig") +endif() + +if("drawtext" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libharfbuzz") +else() + set(OPTIONS "${OPTIONS} --disable-libharfbuzz") +endif() + +if("freetype" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libfreetype") +else() + set(OPTIONS "${OPTIONS} --disable-libfreetype") +endif() + +if("fribidi" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libfribidi") +else() + set(OPTIONS "${OPTIONS} --disable-libfribidi") +endif() + +if("iconv" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-iconv") + set(WITH_ICONV ON) +else() + set(OPTIONS "${OPTIONS} --disable-iconv") + set(WITH_ICONV OFF) +endif() + +if("ilbc" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libilbc") + set(WITH_ILBC ON) +else() + set(OPTIONS "${OPTIONS} --disable-libilbc") + set(WITH_ILBC OFF) +endif() + +if("lzma" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-lzma") + set(WITH_LZMA ON) +else() + set(OPTIONS "${OPTIONS} --disable-lzma") + set(WITH_LZMA OFF) +endif() + +if("mp3lame" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libmp3lame") + set(WITH_MP3LAME ON) +else() + set(OPTIONS "${OPTIONS} --disable-libmp3lame") + set(WITH_MP3LAME OFF) +endif() + +if("modplug" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libmodplug") + set(WITH_MODPLUG ON) +else() + set(OPTIONS "${OPTIONS} --disable-libmodplug") + set(WITH_MODPLUG OFF) +endif() + +if("nvcodec" IN_LIST FEATURES) + #Note: the --enable-cuda option does not actually require the cuda sdk or toolset port dependency as ffmpeg uses runtime detection and dynamic loading + set(OPTIONS "${OPTIONS} --enable-cuda --enable-nvenc --enable-nvdec --enable-cuvid --enable-ffnvcodec") +else() + set(OPTIONS "${OPTIONS} --disable-cuda --disable-nvenc --disable-nvdec --disable-cuvid --disable-ffnvcodec") +endif() + +if("opencl" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-opencl") + set(WITH_OPENCL ON) +else() + set(OPTIONS "${OPTIONS} --disable-opencl") + set(WITH_OPENCL OFF) +endif() + +if("opengl" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-opengl") +else() + set(OPTIONS "${OPTIONS} --disable-opengl") +endif() + +if("openh264" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libopenh264") + set(WITH_OPENH264 ON) +else() + set(OPTIONS "${OPTIONS} --disable-libopenh264") + set(WITH_OPENH264 OFF) +endif() + +if("openjpeg" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libopenjpeg") + set(WITH_OPENJPEG ON) +else() + set(OPTIONS "${OPTIONS} --disable-libopenjpeg") + set(WITH_OPENJPEG OFF) +endif() + +if("openmpt" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libopenmpt") + set(WITH_OPENMPT ON) +else() + set(OPTIONS "${OPTIONS} --disable-libopenmpt") + set(WITH_OPENMPT OFF) +endif() + +if("openssl" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-openssl") +else() + set(OPTIONS "${OPTIONS} --disable-openssl") + if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP) + string(APPEND OPTIONS " --enable-schannel") + endif() +endif() + +if("opus" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libopus") + set(WITH_OPUS ON) +else() + set(OPTIONS "${OPTIONS} --disable-libopus") + set(WITH_OPUS OFF) +endif() + +if("sdl2" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-sdl2") +else() + set(OPTIONS "${OPTIONS} --disable-sdl2") +endif() + +if("snappy" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libsnappy") + set(WITH_SNAPPY ON) +else() + set(OPTIONS "${OPTIONS} --disable-libsnappy") + set(WITH_SNAPPY OFF) +endif() + +if("soxr" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libsoxr") + set(WITH_SOXR ON) +else() + set(OPTIONS "${OPTIONS} --disable-libsoxr") + set(WITH_SOXR OFF) +endif() + +if("speex" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libspeex") + set(WITH_SPEEX ON) +else() + set(OPTIONS "${OPTIONS} --disable-libspeex") + set(WITH_SPEEX OFF) +endif() + +if("ssh" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libssh") + set(WITH_SSH ON) +else() + set(OPTIONS "${OPTIONS} --disable-libssh") + set(WITH_SSH OFF) +endif() + +if("tensorflow" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libtensorflow") +else() + set(OPTIONS "${OPTIONS} --disable-libtensorflow") +endif() + +if("tesseract" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libtesseract") +else() + set(OPTIONS "${OPTIONS} --disable-libtesseract") +endif() + +if("theora" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libtheora") + set(WITH_THEORA ON) +else() + set(OPTIONS "${OPTIONS} --disable-libtheora") + set(WITH_THEORA OFF) +endif() + +if("vorbis" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libvorbis") + set(WITH_VORBIS ON) +else() + set(OPTIONS "${OPTIONS} --disable-libvorbis") + set(WITH_VORBIS OFF) +endif() + +if("vpx" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libvpx") + set(WITH_VPX ON) +else() + set(OPTIONS "${OPTIONS} --disable-libvpx") + set(WITH_VPX OFF) +endif() + +if("vulkan" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-vulkan") +else() + set(OPTIONS "${OPTIONS} --disable-vulkan") +endif() + +if("webp" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libwebp") + set(WITH_WEBP ON) +else() + set(OPTIONS "${OPTIONS} --disable-libwebp") + set(WITH_WEBP OFF) +endif() + +if("x264" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libx264") + set(WITH_X264 ON) +else() + set(OPTIONS "${OPTIONS} --disable-libx264") + set(WITH_X264 OFF) +endif() + +if("x265" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libx265") + set(WITH_X265 ON) +else() + set(OPTIONS "${OPTIONS} --disable-libx265") + set(WITH_X265 OFF) +endif() + +if("xml2" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libxml2") + set(WITH_XML2 ON) +else() + set(OPTIONS "${OPTIONS} --disable-libxml2") + set(WITH_XML2 OFF) +endif() + +if("zlib" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-zlib") +else() + set(OPTIONS "${OPTIONS} --disable-zlib") +endif() + +if ("srt" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libsrt") + set(WITH_SRT ON) +else() + set(OPTIONS "${OPTIONS} --disable-libsrt") + set(WITH_SRT OFF) +endif() + +if ("qsv" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libmfx --enable-encoder=h264_qsv --enable-decoder=h264_qsv") + set(WITH_MFX ON) +else() + set(OPTIONS "${OPTIONS} --disable-libmfx") + set(WITH_MFX OFF) +endif() + +if ("vaapi" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-vaapi") + set(WITH_VAAPI ON) +else() + set(OPTIONS "${OPTIONS} --disable-vaapi") + set(WITH_VAAPI OFF) +endif() + +if("zmq" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-libzmq") + set(WITH_ZMQ ON) +else() + set(OPTIONS "${OPTIONS} --disable-libzmq") + set(WITH_ZMQ OFF) +endif() + +if("rubberband" IN_LIST FEATURES) + set(OPTIONS "${OPTIONS} --enable-librubberband") + set(WITH_RUBBERBAND ON) +else() + set(OPTIONS "${OPTIONS} --disable-librubberband") + set(WITH_RUBBERBAND OFF) +endif() + +set(OPTIONS_CROSS "--enable-cross-compile") + +# ffmpeg needs --cross-prefix option to use appropriate tools for cross-compiling. +if(VCPKG_DETECTED_CMAKE_C_COMPILER MATCHES "([^\/]*-)gcc$") + string(APPEND OPTIONS_CROSS " --cross-prefix=${CMAKE_MATCH_1}") +endif() + +if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + set(BUILD_ARCH "x86_64") +else() + set(BUILD_ARCH ${VCPKG_TARGET_ARCHITECTURE}) +endif() + +if (VCPKG_TARGET_ARCHITECTURE STREQUAL "arm" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64") + if(VCPKG_TARGET_IS_WINDOWS) + vcpkg_find_acquire_program(GASPREPROCESSOR) + foreach(GAS_PATH ${GASPREPROCESSOR}) + get_filename_component(GAS_ITEM_PATH ${GAS_PATH} DIRECTORY) + vcpkg_add_to_path("${GAS_ITEM_PATH}") + endforeach(GAS_PATH) + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + set(ENV{LIBPATH} "$ENV{LIBPATH};$ENV{_WKITS10}references\\windows.foundation.foundationcontract\\2.0.0.0\\;$ENV{_WKITS10}references\\windows.foundation.universalapicontract\\3.0.0.0\\") + string(APPEND OPTIONS " --disable-programs") + string(APPEND OPTIONS " --extra-cflags=-DWINAPI_FAMILY=WINAPI_FAMILY_APP --extra-cflags=-D_WIN32_WINNT=0x0A00") + string(APPEND OPTIONS " --extra-ldflags=-APPCONTAINER --extra-ldflags=WindowsApp.lib") +endif() + +if (VCPKG_TARGET_IS_IOS) + set(vcpkg_target_arch "${VCPKG_TARGET_ARCHITECTURE}") + if (VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + set(vcpkg_target_arch "x86_64") + elseif (VCPKG_TARGET_ARCHITECTURE STREQUAL "arm") + message(FATAL_ERROR "You can build for arm up to iOS 10 but ffmpeg can only be built for iOS 11.0 and later. + Did you mean arm64?") + elseif (VCPKG_TARGET_ARCHITECTURE STREQUAL "x86") + message(FATAL_ERROR "You can build for x86 up to iOS 10 but ffmpeg can only be built for iOS 11.0 and later. + Did you mean x64") + endif () + + set(vcpkg_osx_deployment_target "${VCPKG_OSX_DEPLOYMENT_TARGET}") + if (NOT VCPKG_OSX_DEPLOYMENT_TARGET) + set(vcpkg_osx_deployment_target 11.0) + elseif (VCPKG_OSX_DEPLOYMENT_TARGET LESS 11.0) # nowadays ffmpeg needs to be built for ios 11.0 and later + message(FATAL_ERROR "ffmpeg can be built only for iOS 11.0 and later but you set VCPKG_OSX_DEPLOYMENT_TARGET to + ${VCPKG_OSX_DEPLOYMENT_TARGET}") + endif () + + if (VCPKG_OSX_SYSROOT STREQUAL "iphonesimulator") + set(simulator "-simulator") + endif () + + set(OPTIONS "${OPTIONS} --extra-cflags=--target=${vcpkg_target_arch}-apple-ios${vcpkg_osx_deployment_target}${simulator}") + set(OPTIONS "${OPTIONS} --extra-ldflags=--target=${vcpkg_target_arch}-apple-ios${vcpkg_osx_deployment_target}${simulator}") + + set(vcpkg_osx_sysroot "${VCPKG_OSX_SYSROOT}") + # only on x64 for some reason you need to specify the sdk path, otherwise it will try to build with the MacOS sdk + # (on apple silicon it's not required but shouldn't cause any problems) + if ((VCPKG_OSX_SYSROOT MATCHES "^(iphoneos|iphonesimulator)$") OR (NOT VCPKG_OSX_SYSROOT) OR (VCPKG_OSX_SYSROOT STREQUAL "")) # if it's not a path + if (VCPKG_OSX_SYSROOT MATCHES "^(iphoneos|iphonesimulator)$") + set(requested_sysroot "${VCPKG_OSX_SYSROOT}") + elseif (VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64") # if the sysroot is not specified we have to guess + set(requested_sysroot "iphoneos") + elseif (VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + set(requested_sysroot "iphonesimulator") + else () + message(FATAL_ERROR "Unsupported build arch: ${VCPKG_TARGET_ARCHITECTURE}") + endif () + message(STATUS "Retrieving default SDK for ${requested_sysroot}") + execute_process( + COMMAND /usr/bin/xcrun --sdk ${requested_sysroot} --show-sdk-path + OUTPUT_VARIABLE sdk_path + ERROR_VARIABLE xcrun_error + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE + ) + if (sdk_path) + message(STATUS "Found!") + set(vcpkg_osx_sysroot "${sdk_path}") + else () + message(FATAL_ERROR "Can't determine ${CMAKE_OSX_SYSROOT} SDK path. Error: ${xcrun_error}") + endif () + endif () + set(OPTIONS "${OPTIONS} --extra-cflags=-isysroot\"${vcpkg_osx_sysroot}\"") + set(OPTIONS "${OPTIONS} --extra-ldflags=-isysroot\"${vcpkg_osx_sysroot}\"") +endif () + +set(OPTIONS_DEBUG "--disable-optimizations") +set(OPTIONS_RELEASE "--enable-optimizations") + +set(OPTIONS "${OPTIONS} ${OPTIONS_CROSS}") + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + set(OPTIONS "${OPTIONS} --disable-static --enable-shared") +endif() + +if(VCPKG_TARGET_IS_MINGW) + set(OPTIONS "${OPTIONS} --extra_cflags=-D_WIN32_WINNT=0x0601") +elseif(VCPKG_TARGET_IS_WINDOWS) + set(OPTIONS "${OPTIONS} --extra-cflags=-DHAVE_UNISTD_H=0") +endif() + +set(maybe_needed_libraries -lm) +separate_arguments(standard_libraries NATIVE_COMMAND "${VCPKG_DETECTED_CMAKE_C_STANDARD_LIBRARIES}") +foreach(item IN LISTS standard_libraries) + if(item IN_LIST maybe_needed_libraries) + set(OPTIONS "${OPTIONS} \"--extra-libs=${item}\"") + endif() +endforeach() + +vcpkg_find_acquire_program(PKGCONFIG) +set(OPTIONS "${OPTIONS} --pkg-config=\"${PKGCONFIG}\"") +if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") + set(OPTIONS "${OPTIONS} --pkg-config-flags=--static") +endif() + +message(STATUS "Building Options: ${OPTIONS}") + +# Release build +if (NOT VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + if (VCPKG_DETECTED_MSVC) + set(OPTIONS_RELEASE "${OPTIONS_RELEASE} --extra-ldflags=-libpath:\"${CURRENT_INSTALLED_DIR}/lib\"") + else() + set(OPTIONS_RELEASE "${OPTIONS_RELEASE} --extra-ldflags=-L\"${CURRENT_INSTALLED_DIR}/lib\"") + endif() + message(STATUS "Building Release Options: ${OPTIONS_RELEASE}") + message(STATUS "Building ${PORT} for Release") + file(MAKE_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel") + # We use response files here as the only known way to handle spaces in paths + set(crsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/cflags.rsp") + string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_C_FLAGS_RELEASE_SANITIZED "${VCPKG_COMBINED_C_FLAGS_RELEASE}") + file(WRITE "${crsp}" "${VCPKG_COMBINED_C_FLAGS_RELEASE_SANITIZED}") + set(ldrsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/ldflags.rsp") + string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_SHARED_LINKER_FLAGS_RELEASE_SANITIZED "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_RELEASE}") + file(WRITE "${ldrsp}" "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_RELEASE_SANITIZED}") + set(ENV{CFLAGS} "@${crsp}") + # All tools except the msvc arm{,64} assembler accept @... as response file syntax. + # For that assembler, there is no known way to pass in flags. We must hope that not passing flags will work acceptably. + if(NOT VCPKG_DETECTED_MSVC OR NOT VCPKG_TARGET_ARCHITECTURE MATCHES "^arm") + set(ENV{ASFLAGS} "@${crsp}") + endif() + set(ENV{LDFLAGS} "@${ldrsp}") + set(ENV{ARFLAGS} "${VCPKG_COMBINED_STATIC_LINKER_FLAGS_RELEASE}") + + set(BUILD_DIR "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel") + set(CONFIGURE_OPTIONS "${OPTIONS} ${OPTIONS_RELEASE}") + set(INST_PREFIX "${CURRENT_PACKAGES_DIR}") + + configure_file("${CMAKE_CURRENT_LIST_DIR}/build.sh.in" "${BUILD_DIR}/build.sh" @ONLY) + + z_vcpkg_setup_pkgconfig_path(CONFIG RELEASE) + + vcpkg_execute_required_process( + COMMAND "${SHELL}" ./build.sh + WORKING_DIRECTORY "${BUILD_DIR}" + LOGNAME "build-${TARGET_TRIPLET}-rel" + SAVE_LOG_FILES ffbuild/config.log + ) + + z_vcpkg_restore_pkgconfig_path() +endif() + +# Debug build +if (NOT VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + if (VCPKG_DETECTED_MSVC) + set(OPTIONS_DEBUG "${OPTIONS_DEBUG} --extra-ldflags=-libpath:\"${CURRENT_INSTALLED_DIR}/debug/lib\"") + else() + set(OPTIONS_DEBUG "${OPTIONS_DEBUG} --extra-ldflags=-L\"${CURRENT_INSTALLED_DIR}/debug/lib\"") + endif() + message(STATUS "Building Debug Options: ${OPTIONS_DEBUG}") + message(STATUS "Building ${PORT} for Debug") + file(MAKE_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg") + set(crsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/cflags.rsp") + string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_C_FLAGS_DEBUG_SANITIZED "${VCPKG_COMBINED_C_FLAGS_DEBUG}") + file(WRITE "${crsp}" "${VCPKG_COMBINED_C_FLAGS_DEBUG_SANITIZED}") + set(ldrsp "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg/ldflags.rsp") + string(REGEX REPLACE "-arch [A-Za-z0-9_]+" "" VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG_SANITIZED "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG}") + file(WRITE "${ldrsp}" "${VCPKG_COMBINED_SHARED_LINKER_FLAGS_DEBUG_SANITIZED}") + set(ENV{CFLAGS} "@${crsp}") + if(NOT VCPKG_DETECTED_MSVC OR NOT VCPKG_TARGET_ARCHITECTURE MATCHES "^arm") + set(ENV{ASFLAGS} "@${crsp}") + endif() + set(ENV{LDFLAGS} "@${ldrsp}") + set(ENV{ARFLAGS} "${VCPKG_COMBINED_STATIC_LINKER_FLAGS_DEBUG}") + + set(BUILD_DIR "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg") + set(CONFIGURE_OPTIONS "${OPTIONS} ${OPTIONS_DEBUG}") + set(INST_PREFIX "${CURRENT_PACKAGES_DIR}/debug") + + configure_file("${CMAKE_CURRENT_LIST_DIR}/build.sh.in" "${BUILD_DIR}/build.sh" @ONLY) + + z_vcpkg_setup_pkgconfig_path(CONFIG DEBUG) + + vcpkg_execute_required_process( + COMMAND "${SHELL}" ./build.sh + WORKING_DIRECTORY "${BUILD_DIR}" + LOGNAME "build-${TARGET_TRIPLET}-dbg" + SAVE_LOG_FILES ffbuild/config.log + ) + + z_vcpkg_restore_pkgconfig_path() +endif() + +if(VCPKG_TARGET_IS_WINDOWS) + file(GLOB DEF_FILES "${CURRENT_PACKAGES_DIR}/lib/*.def" "${CURRENT_PACKAGES_DIR}/debug/lib/*.def") + + if(NOT VCPKG_TARGET_IS_MINGW) + if(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm") + set(LIB_MACHINE_ARG /machine:ARM) + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64") + set(LIB_MACHINE_ARG /machine:ARM64) + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86") + set(LIB_MACHINE_ARG /machine:x86) + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + set(LIB_MACHINE_ARG /machine:x64) + else() + message(FATAL_ERROR "Unsupported target architecture") + endif() + + foreach(DEF_FILE ${DEF_FILES}) + get_filename_component(DEF_FILE_DIR "${DEF_FILE}" DIRECTORY) + get_filename_component(DEF_FILE_NAME "${DEF_FILE}" NAME) + string(REGEX REPLACE "-[0-9]*\\.def" "${VCPKG_TARGET_STATIC_LIBRARY_SUFFIX}" OUT_FILE_NAME "${DEF_FILE_NAME}") + file(TO_NATIVE_PATH "${DEF_FILE}" DEF_FILE_NATIVE) + file(TO_NATIVE_PATH "${DEF_FILE_DIR}/${OUT_FILE_NAME}" OUT_FILE_NATIVE) + message(STATUS "Generating ${OUT_FILE_NATIVE}") + vcpkg_execute_required_process( + COMMAND lib.exe "/def:${DEF_FILE_NATIVE}" "/out:${OUT_FILE_NATIVE}" ${LIB_MACHINE_ARG} + WORKING_DIRECTORY "${CURRENT_PACKAGES_DIR}" + LOGNAME "libconvert-${TARGET_TRIPLET}" + ) + endforeach() + endif() + + file(GLOB EXP_FILES "${CURRENT_PACKAGES_DIR}/lib/*.exp" "${CURRENT_PACKAGES_DIR}/debug/lib/*.exp") + file(GLOB LIB_FILES "${CURRENT_PACKAGES_DIR}/bin/*${VCPKG_TARGET_STATIC_LIBRARY_SUFFIX}" "${CURRENT_PACKAGES_DIR}/debug/bin/*${VCPKG_TARGET_STATIC_LIBRARY_SUFFIX}") + if(VCPKG_TARGET_IS_MINGW) + file(GLOB LIB_FILES_2 "${CURRENT_PACKAGES_DIR}/bin/*.lib" "${CURRENT_PACKAGES_DIR}/debug/bin/*.lib") + endif() + set(files_to_remove ${EXP_FILES} ${LIB_FILES} ${LIB_FILES_2} ${DEF_FILES}) + if(files_to_remove) + file(REMOVE ${files_to_remove}) + endif() +endif() + +if("ffmpeg" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES ffmpeg AUTO_CLEAN) +endif() +if("ffprobe" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES ffprobe AUTO_CLEAN) +endif() +if("ffplay" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES ffplay AUTO_CLEAN) +endif() + +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include" "${CURRENT_PACKAGES_DIR}/debug/share") + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "static") + file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin" "${CURRENT_PACKAGES_DIR}/debug/bin") +endif() + +vcpkg_copy_pdbs() + +if(VCPKG_TARGET_IS_WINDOWS) + file(GLOB pc_files "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/*.pc" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/*.pc") + foreach(file IN LISTS pc_files) + # ffmpeg exports -libpath:foo and bar.lib for transitive deps. + # But CMake's pkg_check_modules cannot handle this properly. + # pc files generally use non-msvc syntax with -Lfoo -lbar. + file(READ "${file}" content) + foreach(entry IN ITEMS Libs Libs.private) + if(content MATCHES "${entry}:( [^\n]*)") + set(old_value "${CMAKE_MATCH_1}") + string(REGEX REPLACE "-libpath:" "-L" new_value "${old_value}") + string(REGEX REPLACE " ([^ /]+)[.]lib" " -l\\1" new_value "${new_value}") + string(REPLACE "${entry}:${old_value}" "${entry}:${new_value}" content "${content}") + endif() + endforeach() + file(WRITE "${file}" "${content}") + endforeach() +endif() +vcpkg_fixup_pkgconfig() + +# Handle dependencies + +x_vcpkg_pkgconfig_get_modules(PREFIX FFMPEG_PKGCONFIG MODULES ${FFMPEG_PKGCONFIG_MODULES} LIBS) + +function(append_dependencies_from_libs out) + cmake_parse_arguments(PARSE_ARGV 1 "arg" "" "LIBS" "") + separate_arguments(contents UNIX_COMMAND "${arg_LIBS}") + list(FILTER contents EXCLUDE REGEX "^-F.+") + list(FILTER contents EXCLUDE REGEX "^-framework$") + list(FILTER contents EXCLUDE REGEX "^-L.+") + list(FILTER contents EXCLUDE REGEX "^-libpath:.+") + list(TRANSFORM contents REPLACE "^-Wl,-framework," "-l") + list(FILTER contents EXCLUDE REGEX "^-Wl,.+") + list(TRANSFORM contents REPLACE "^-l" "") + list(FILTER contents EXCLUDE REGEX "^avutil$") + list(FILTER contents EXCLUDE REGEX "^avcodec$") + list(FILTER contents EXCLUDE REGEX "^avdevice$") + list(FILTER contents EXCLUDE REGEX "^avfilter$") + list(FILTER contents EXCLUDE REGEX "^avformat$") + list(FILTER contents EXCLUDE REGEX "^postproc$") + list(FILTER contents EXCLUDE REGEX "^swresample$") + list(FILTER contents EXCLUDE REGEX "^swscale$") + if(VCPKG_TARGET_IS_WINDOWS) + list(TRANSFORM contents TOLOWER) + endif() + if(contents) + list(APPEND "${out}" "${contents}") + set("${out}" "${${out}}" PARENT_SCOPE) + endif() +endfunction() + +append_dependencies_from_libs(FFMPEG_DEPENDENCIES_RELEASE LIBS "${FFMPEG_PKGCONFIG_LIBS_RELEASE}") +append_dependencies_from_libs(FFMPEG_DEPENDENCIES_DEBUG LIBS "${FFMPEG_PKGCONFIG_LIBS_DEBUG}") + +# must remove duplicates from the front to respect link order so reverse first +list(REVERSE FFMPEG_DEPENDENCIES_RELEASE) +list(REVERSE FFMPEG_DEPENDENCIES_DEBUG) +list(REMOVE_DUPLICATES FFMPEG_DEPENDENCIES_RELEASE) +list(REMOVE_DUPLICATES FFMPEG_DEPENDENCIES_DEBUG) +list(REVERSE FFMPEG_DEPENDENCIES_RELEASE) +list(REVERSE FFMPEG_DEPENDENCIES_DEBUG) + +message(STATUS "Dependencies (release): ${FFMPEG_DEPENDENCIES_RELEASE}") +message(STATUS "Dependencies (debug): ${FFMPEG_DEPENDENCIES_DEBUG}") + +# Handle version strings + +function(extract_regex_from_file out) + cmake_parse_arguments(PARSE_ARGV 1 "arg" "MAJOR" "FILE_WITHOUT_EXTENSION;REGEX" "") + file(READ "${arg_FILE_WITHOUT_EXTENSION}.h" contents) + if (contents MATCHES "${arg_REGEX}") + if(NOT CMAKE_MATCH_COUNT EQUAL 1) + message(FATAL_ERROR "Could not identify match group in regular expression \"${arg_REGEX}\"") + endif() + else() + if (arg_MAJOR) + file(READ "${arg_FILE_WITHOUT_EXTENSION}_major.h" contents) + if (contents MATCHES "${arg_REGEX}") + if(NOT CMAKE_MATCH_COUNT EQUAL 1) + message(FATAL_ERROR "Could not identify match group in regular expression \"${arg_REGEX}\"") + endif() + else() + message(WARNING "Could not find line matching \"${arg_REGEX}\" in file \"${arg_FILE_WITHOUT_EXTENSION}_major.h\"") + endif() + else() + message(WARNING "Could not find line matching \"${arg_REGEX}\" in file \"${arg_FILE_WITHOUT_EXTENSION}.h\"") + endif() + endif() + set("${out}" "${CMAKE_MATCH_1}" PARENT_SCOPE) +endfunction() + +function(extract_version_from_component out) + cmake_parse_arguments(PARSE_ARGV 1 "arg" "" "COMPONENT" "") + string(TOLOWER "${arg_COMPONENT}" component_lower) + string(TOUPPER "${arg_COMPONENT}" component_upper) + extract_regex_from_file(major_version + FILE_WITHOUT_EXTENSION "${SOURCE_PATH}/${component_lower}/version" + MAJOR + REGEX "#define ${component_upper}_VERSION_MAJOR[ ]+([0-9]+)" + ) + extract_regex_from_file(minor_version + FILE_WITHOUT_EXTENSION "${SOURCE_PATH}/${component_lower}/version" + REGEX "#define ${component_upper}_VERSION_MINOR[ ]+([0-9]+)" + ) + extract_regex_from_file(micro_version + FILE_WITHOUT_EXTENSION "${SOURCE_PATH}/${component_lower}/version" + REGEX "#define ${component_upper}_VERSION_MICRO[ ]+([0-9]+)" + ) + set("${out}" "${major_version}.${minor_version}.${micro_version}" PARENT_SCOPE) +endfunction() + +extract_regex_from_file(FFMPEG_VERSION + FILE_WITHOUT_EXTENSION "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel/libavutil/ffversion" + REGEX "#define FFMPEG_VERSION[ ]+\"(.+)\"" +) + +extract_version_from_component(LIBAVUTIL_VERSION + COMPONENT libavutil) +extract_version_from_component(LIBAVCODEC_VERSION + COMPONENT libavcodec) +extract_version_from_component(LIBAVDEVICE_VERSION + COMPONENT libavdevice) +extract_version_from_component(LIBAVFILTER_VERSION + COMPONENT libavfilter) +extract_version_from_component(LIBAVFORMAT_VERSION + COMPONENT libavformat) +extract_version_from_component(LIBSWRESAMPLE_VERSION + COMPONENT libswresample) +extract_version_from_component(LIBSWSCALE_VERSION + COMPONENT libswscale) + +# Handle copyright +file(STRINGS "${CURRENT_BUILDTREES_DIR}/build-${TARGET_TRIPLET}-rel-out.log" LICENSE_STRING REGEX "License: .*" LIMIT_COUNT 1) +if(LICENSE_STRING STREQUAL "License: LGPL version 2.1 or later") + set(LICENSE_FILE "COPYING.LGPLv2.1") +elseif(LICENSE_STRING STREQUAL "License: LGPL version 3 or later") + set(LICENSE_FILE "COPYING.LGPLv3") +elseif(LICENSE_STRING STREQUAL "License: GPL version 2 or later") + set(LICENSE_FILE "COPYING.GPLv2") +elseif(LICENSE_STRING STREQUAL "License: GPL version 3 or later") + set(LICENSE_FILE "COPYING.GPLv3") +elseif(LICENSE_STRING STREQUAL "License: nonfree and unredistributable") + set(LICENSE_FILE "COPYING.NONFREE") + file(WRITE "${SOURCE_PATH}/${LICENSE_FILE}" "${LICENSE_STRING}") +else() + message(FATAL_ERROR "Failed to identify license (${LICENSE_STRING})") +endif() + +configure_file("${CMAKE_CURRENT_LIST_DIR}/FindFFMPEG.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/FindFFMPEG.cmake" @ONLY) +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +if(VCPKG_LIBRARY_LINKAGE STREQUAL "static" AND NOT VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_OSX AND NOT VCPKG_TARGET_IS_IOS) + file(APPEND "${CURRENT_PACKAGES_DIR}/share/${PORT}/usage" " +To use the static libraries to build your own shared library, +you may need to add the following link option for your library: + + -Wl,-Bsymbolic +") +endif() + +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/${LICENSE_FILE}") diff --git a/portfiles/ffmpeg/usage b/portfiles/ffmpeg/usage new file mode 100644 index 000000000..b2d02a089 --- /dev/null +++ b/portfiles/ffmpeg/usage @@ -0,0 +1,29 @@ +ffmpeg provides CMake integration: + + find_package(FFMPEG REQUIRED) + target_include_directories(main PRIVATE ${FFMPEG_INCLUDE_DIRS}) + target_link_directories(main PRIVATE ${FFMPEG_LIBRARY_DIRS}) + target_link_libraries(main PRIVATE ${FFMPEG_LIBRARIES}) + +ffmpeg provides pkg-config modules: + + # FFmpeg codec library + libavcodec + + # FFmpeg device handling library + libavdevice + + # FFmpeg audio/video filtering library + libavfilter + + # FFmpeg container format library + libavformat + + # FFmpeg utility library + libavutil + + # FFmpeg audio resampling library + libswresample + + # FFmpeg image rescaling library + libswscale diff --git a/portfiles/ffmpeg/vcpkg-cmake-wrapper.cmake b/portfiles/ffmpeg/vcpkg-cmake-wrapper.cmake new file mode 100644 index 000000000..2f1843a4c --- /dev/null +++ b/portfiles/ffmpeg/vcpkg-cmake-wrapper.cmake @@ -0,0 +1,334 @@ +set(FFMPEG_PREV_MODULE_PATH ${CMAKE_MODULE_PATH}) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) + +cmake_policy(SET CMP0012 NEW) + +# Detect if we use "our" find module or a vendored one +set(z_vcpkg_using_vcpkg_find_ffmpeg OFF) + +# Detect targets created e.g. by VTK/CMake/FindFFMPEG.cmake +set(vcpkg_no_avcodec_target ON) +set(vcpkg_no_avformat_target ON) +set(vcpkg_no_avutil_target ON) +set(vcpkg_no_swresample_target ON) +if(TARGET FFmpeg::avcodec) + set(vcpkg_no_avcodec_target OFF) +endif() +if(TARGET FFmpeg::avformat) + set(vcpkg_no_avformat_target OFF) +endif() +if(TARGET FFmpeg::avutil) + set(vcpkg_no_avutil_target OFF) +endif() +if(TARGET FFmpeg::swresample) + set(vcpkg_no_swresample_target OFF) +endif() + +_find_package(${ARGS}) + +# Fixup of variables and targets for (some) vendored find modules +if(NOT z_vcpkg_using_vcpkg_find_ffmpeg AND NOT "@BUILD_SHARED_LIBS@") + +include(SelectLibraryConfigurations) + +if(CMAKE_HOST_WIN32) + set(PKG_CONFIG_EXECUTABLE "${CMAKE_CURRENT_LIST_DIR}/../../../@_HOST_TRIPLET@/tools/pkgconf/pkgconf.exe" CACHE STRING "" FORCE) +endif() +set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) # Required for CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 3.1 which otherwise ignores CMAKE_PREFIX_PATH +find_package(PkgConfig) + +pkg_check_modules(PKG_libavcodec libavcodec) +if(APPLE) + list(FILTER PKG_libavcodec_LIBRARIES INCLUDE REGEX "^-framework ") + if(PKG_libavcodec_LIBRARIES) + list(APPEND FFMPEG_LIBRARIES ${PKG_libavcodec_LIBRARIES}) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE ${PKG_libavcodec_LIBRARIES}) + endif() + endif() +elseif(WIN32) + foreach(z_libavcodec_link_lib IN ITEMS bcrypt crypt32 mfuuid psapi secur32 shlwapi strmiids vfw32 ws2_32 usp10 cfgmgr32 rpcrt4) + if("-l${z_libavcodec_link_lib}" IN_LIST PKG_libavcodec_LDFLAGS) + list(APPEND FFMPEG_LIBRARIES "${z_libavcodec_link_lib}") + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE "$") + endif() + endif() + endforeach() +endif() + +if(@WITH_MP3LAME@) + find_package(mp3lame CONFIG ) + list(APPEND FFMPEG_LIBRARIES mp3lame::mp3lame) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + # target exists after find_package and wasn't defined before + target_link_libraries(FFmpeg::avcodec INTERFACE mp3lame::mp3lame) + endif() +endif() + +if(@WITH_XML2@) + find_package(LibXml2 ) + list(APPEND FFMPEG_LIBRARIES LibXml2::LibXml2) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE LibXml2::LibXml2) + endif() + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE LibXml2::LibXml2) + endif() +endif() + +if(@WITH_ICONV@) + find_package(Iconv ) + list(APPEND FFMPEG_LIBRARIES Iconv::Iconv) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE Iconv::Iconv) + endif() + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE Iconv::Iconv) + endif() +endif() + +if(@WITH_LZMA@) + find_package(liblzma CONFIG ) + list(APPEND FFMPEG_LIBRARIES liblzma::liblzma) + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE liblzma::liblzma) + endif() +endif() + +if(@WITH_SSH@) + find_package(libssh CONFIG ) + list(APPEND FFMPEG_LIBRARIES ssh) + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE ssh) + endif() +endif() + +if(@WITH_OPENMPT@) + find_package(libopenmpt CONFIG ) + list(APPEND FFMPEG_LIBRARIES libopenmpt::libopenmpt) + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE libopenmpt::libopenmpt) + endif() +endif() + +if(@WITH_MODPLUG@) + pkg_check_modules(modplug IMPORTED_TARGET libmodplug) + list(APPEND FFMPEG_LIBRARIES PkgConfig::modplug) + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE PkgConfig::modplug) + endif() +endif() + +if(@WITH_SRT@) + pkg_check_modules(srt IMPORTED_TARGET srt) + list(APPEND FFMPEG_LIBRARIES PkgConfig::srt) + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE PkgConfig::srt) + endif() +endif() + +if(@WITH_DAV1D@) + pkg_check_modules(dav1d IMPORTED_TARGET dav1d) + list(APPEND FFMPEG_LIBRARIES PkgConfig::dav1d) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::dav1d) + endif() +endif() + +if(@WITH_OPENH264@) + pkg_check_modules(openh264 IMPORTED_TARGET openh264) + list(APPEND FFMPEG_LIBRARIES PkgConfig::openh264) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::openh264) + endif() +endif() + +if(@WITH_WEBP@) + find_package(WebP CONFIG ) + list(APPEND FFMPEG_LIBRARIES WebP::webp WebP::webpdecoder WebP::webpdemux WebP::libwebpmux) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE WebP::webp WebP::webpdecoder WebP::webpdemux WebP::libwebpmux) + endif() +endif() + +if(@WITH_SOXR@) + find_library(SOXR_LIBRARY_RELEASE NAMES soxr PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/lib" NO_DEFAULT_PATH) + find_library(SOXR_LIBRARY_DEBUG NAMES soxr PATHS "${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/debug/lib" NO_DEFAULT_PATH) + select_library_configurations(SOXR) + if(SOXR_LIBRARY_RELEASE) + list(APPEND FFMPEG_LIBRARIES $<$>:${SOXR_LIBRARY_RELEASE}>) + endif() + if(SOXR_LIBRARY_DEBUG) + list(APPEND FFMPEG_LIBRARIES $<$:${SOXR_LIBRARY_DEBUG}>) + endif() + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE ${SOXR_LIBRARIES}) + endif() + if(vcpkg_no_swresample_target AND TARGET FFmpeg::swresample) + target_link_libraries(FFmpeg::swresample INTERFACE ${SOXR_LIBRARIES}) + endif() +endif() + +if(@WITH_THEORA@) + pkg_check_modules(theora IMPORTED_TARGET theora) + list(APPEND FFMPEG_LIBRARIES PkgConfig::theora) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::theora) + endif() +endif() + +if(@WITH_MFX@) + pkg_check_modules(libmfx IMPORTED_TARGET libmfx) + list(APPEND FFMPEG_LIBRARIES PkgConfig::libmfx) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::libmfx) + endif() + if(vcpkg_no_avutil_target AND TARGET FFmpeg::avutil) + target_link_libraries(FFmpeg::avutil INTERFACE PkgConfig::libmfx) + endif() +endif() + +if(@WITH_ILBC@) + pkg_check_modules(libilbc IMPORTED_TARGET libilbc) + list(APPEND FFMPEG_LIBRARIES PkgConfig::libilbc) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::libilbc) + endif() +endif() + +if(@WITH_THEORA@) + pkg_check_modules(theora IMPORTED_TARGET theora) + list(APPEND FFMPEG_LIBRARIES PkgConfig::theora) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::theora) + endif() +endif() + +if(@WITH_VORBIS@) + find_package(Vorbis CONFIG ) + list(APPEND FFMPEG_LIBRARIES Vorbis::vorbis Vorbis::vorbisenc) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE Vorbis::vorbis Vorbis::vorbisenc) + endif() +endif() + +if(@WITH_VPX@) + pkg_check_modules(vpx IMPORTED_TARGET vpx) + list(APPEND FFMPEG_LIBRARIES PkgConfig::vpx) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::vpx) + endif() +endif() + +if(@WITH_OPUS@) + find_package(Opus CONFIG ) + list(APPEND FFMPEG_LIBRARIES Opus::opus) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE Opus::opus) + endif() +endif() + +if(@WITH_SPEEX@) + pkg_check_modules(speex IMPORTED_TARGET speex) + list(APPEND FFMPEG_LIBRARIES PkgConfig::speex) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::speex) + endif() +endif() + +if(@WITH_OPENJPEG@) + find_package(OpenJPEG CONFIG ) + list(APPEND FFMPEG_LIBRARIES openjp2) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE openjp2) + endif() +endif() + +if(@WITH_SNAPPY@) + find_package(Snappy CONFIG ) + list(APPEND FFMPEG_LIBRARIES Snappy::snappy) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE Snappy::snappy) + endif() +endif() + +if(@WITH_AOM@) + pkg_check_modules(aom IMPORTED_TARGET aom) + list(APPEND FFMPEG_LIBRARIES PkgConfig::aom) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::aom) + endif() +endif() + +if(@WITH_X264@) + pkg_check_modules(x264 IMPORTED_TARGET x264) + list(APPEND FFMPEG_LIBRARIES PkgConfig::x264) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::x264) + endif() +endif() + +if(@WITH_X265@) + pkg_check_modules(x265 IMPORTED_TARGET x265) + list(APPEND FFMPEG_LIBRARIES PkgConfig::x265) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::x265) + endif() +endif() + +if(@WITH_AAC@) + find_package(fdk-aac CONFIG) + list(APPEND FFMPEG_LIBRARIES FDK-AAC::fdk-aac) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE FDK-AAC::fdk-aac) + endif() +endif() + +if(@WITH_OPENCL@) + find_package(OpenCL ) + list(APPEND FFMPEG_LIBRARIES OpenCL::OpenCL) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE OpenCL::OpenCL) + endif() + if(vcpkg_no_avutil_target AND TARGET FFmpeg::avutil) + target_link_libraries(FFmpeg::avutil INTERFACE OpenCL::OpenCL) + endif() +endif() + +if(@WITH_VAAPI@) + pkg_check_modules(libva IMPORTED_TARGET libva) + pkg_check_modules(libva-drm IMPORTED_TARGET libva-drm) + list(APPEND FFMPEG_LIBRARIES PkgConfig::libva PkgConfig::libva-drm) + if(vcpkg_no_avcodec_target AND TARGET FFmpeg::avcodec) + target_link_libraries(FFmpeg::avcodec INTERFACE PkgConfig::libva PkgConfig::libva-drm) + endif() +endif() + +if(@WITH_ZMQ@) + pkg_check_modules(libzmq IMPORTED_TARGET libzmq) + list(APPEND FFMPEG_LIBRARIES PkgConfig::libzmq) + if(vcpkg_no_avformat_target AND TARGET FFmpeg::avformat) + target_link_libraries(FFmpeg::avformat INTERFACE PkgConfig::libzmq) + endif() +endif() + +if(@WITH_RUBBERBAND@) + pkg_check_modules(rubberband IMPORTED_TARGET rubberband) + list(APPEND FFMPEG_LIBRARIES PkgConfig::rubberband) + if(vcpkg_no_avfilter_target AND TARGET FFmpeg::avfilter) + target_link_libraries(FFmpeg::avfilter INTERFACE PkgConfig::rubberband) + endif() +endif() + +endif() +unset(z_vcpkg_using_vcpkg_find_ffmpeg) + +set(FFMPEG_LIBRARY ${FFMPEG_LIBRARIES}) + +set(CMAKE_MODULE_PATH ${FFMPEG_PREV_MODULE_PATH}) + +unset(vcpkg_no_avcodec_target) +unset(vcpkg_no_avformat_target) +unset(vcpkg_no_avutil_target) +unset(vcpkg_no_swresample_target) diff --git a/portfiles/ffmpeg/vcpkg.json b/portfiles/ffmpeg/vcpkg.json new file mode 100644 index 000000000..c769a95fc --- /dev/null +++ b/portfiles/ffmpeg/vcpkg.json @@ -0,0 +1,754 @@ +{ + "name": "ffmpeg", + "version": "7.1.2", + "port-version": 5, + "description": [ + "A library to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created.", + "FFmpeg is the leading multimedia framework, able to decode, encode, transcode, mux, demux, stream, filter and play pretty much anything that humans and machines have created. It supports the most obscure ancient formats up to the cutting edge. No matter if they were designed by some standards committee, the community or a corporation. It is also highly portable: FFmpeg compiles, runs, and passes our testing infrastructure FATE across Linux, Mac OS X, Microsoft Windows, the BSDs, Solaris, etc. under a wide variety of build environments, machine architectures, and configurations." + ], + "homepage": "https://ffmpeg.org", + "license": null, + "dependencies": [ + { + "name": "vcpkg-cmake-get-vars", + "host": true + }, + { + "name": "vcpkg-pkgconfig-get-modules", + "host": true + } + ], + "default-features": [ + "avcodec", + "avdevice", + "avfilter", + "avformat", + "swresample", + "swscale" + ], + "features": { + "all": { + "description": "Build with all allowed dependencies selected that are compatible with the lgpl license", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "sdl2" + ] + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "aom", + "avcodec", + "avdevice", + "avfilter", + "avformat", + "bzip2", + "freetype", + "iconv", + "lzma", + "mp3lame", + "openjpeg", + "openmpt", + "opus", + "snappy", + "soxr", + "speex", + "swresample", + "swscale", + "theora", + "vorbis", + "vpx", + "vulkan", + "webp", + "xml2", + "zlib" + ] + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "alsa" + ], + "platform": "linux" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "ass" + ], + "platform": "!uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "fontconfig" + ], + "platform": "!uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "fribidi" + ], + "platform": "!uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "modplug" + ], + "platform": "!uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "openh264" + ], + "platform": "!uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "srt" + ], + "platform": "!uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "ilbc" + ], + "platform": "!(arm & uwp)" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "ssh" + ], + "platform": "!(uwp | arm)" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "amf" + ], + "platform": "!osx & !uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "opencl" + ], + "platform": "!uwp & !osx" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "opengl" + ], + "platform": "!uwp & !(arm64 & windows)" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "tensorflow" + ], + "platform": "x64 & !static & !uwp & !windows" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "dav1d" + ], + "platform": "!(uwp | (windows & x86 & !static))" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "tesseract" + ], + "platform": "!(windows & arm) & !static & !uwp" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "qsv" + ], + "platform": "linux | (!osx & !uwp & !(arm & windows))" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "nvcodec" + ], + "platform": "linux | (!osx & !uwp & !(arm64 & windows))" + } + ] + }, + "all-gpl": { + "description": "Build with all allowed dependencies selected that are compatible with the gpl license", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "all", + "gpl", + "postproc" + ] + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "x264" + ], + "platform": "!(arm & windows)" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "x265" + ], + "platform": "!uwp & !(arm & windows)" + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avisynthplus" + ], + "platform": "windows & !arm & !uwp & !static" + } + ] + }, + "all-nonfree": { + "description": "Build with all allowed dependencies selected with a non-redistributable license", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "all-gpl", + "fdk-aac", + "nonfree", + "openssl" + ] + } + ] + }, + "alsa": { + "description": "Enable ALSA support", + "dependencies": [ + "alsa" + ] + }, + "amf": { + "description": "AMD AMF codec support", + "dependencies": [ + "amd-amf" + ] + }, + "aom": { + "description": "AV1 video encoding/decoding via libaom support in ffmpeg", + "dependencies": [ + "aom" + ] + }, + "ass": { + "description": "Libass subtitles rendering, needed for subtitles and ass filter support in ffmpeg", + "dependencies": [ + "libass" + ] + }, + "avcodec": { + "description": "Build the avcodec library" + }, + "avdevice": { + "description": "Build the avdevice library", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avcodec", + "avformat" + ] + } + ] + }, + "avfilter": { + "description": "Build the avfilter library" + }, + "avformat": { + "description": "Build the avformat library", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avcodec" + ] + } + ] + }, + "avisynthplus": { + "description": "Reading of AviSynth script files", + "supports": "windows & !static", + "dependencies": [ + "avisynthplus", + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "gpl" + ] + } + ] + }, + "avresample": { + "description": "Build the avresample library" + }, + "bzip2": { + "description": "Bzip2 support", + "dependencies": [ + "bzip2" + ] + }, + "dav1d": { + "description": "AV1 decoding via libdav1d", + "dependencies": [ + "dav1d" + ] + }, + "drawtext": { + "description": "Enable the drawtext filter", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "freetype" + ] + }, + "harfbuzz" + ] + }, + "dvdvideo": { + "description": "DVD-Video demuxer", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "gpl" + ] + }, + "libdvdnav", + "libdvdread" + ] + }, + "fdk-aac": { + "description": "AAC de/encoding via libfdk-aac, **including GPL-incompatible patent-encumbered HE-AAC**. If you do not require HE-AAC, use the built-in FFmpeg AAC codec.", + "dependencies": [ + { + "name": "fdk-aac", + "default-features": false, + "features": [ + "he-aac" + ] + }, + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "nonfree" + ] + } + ] + }, + "ffmpeg": { + "description": "Build the ffmpeg application", + "supports": "!uwp", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avcodec", + "avfilter", + "avformat" + ] + } + ] + }, + "ffplay": { + "description": "Build the ffplay application", + "supports": "!uwp", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avcodec", + "avfilter", + "avformat", + "sdl2", + "swresample", + "swscale" + ] + } + ] + }, + "ffprobe": { + "description": "Build the ffprobe application", + "supports": "!uwp", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avcodec", + "avformat" + ] + } + ] + }, + "fontconfig": { + "description": "Useful for drawtext filter", + "dependencies": [ + "fontconfig" + ] + }, + "freetype": { + "description": "Needed for drawtext filter", + "dependencies": [ + "freetype" + ] + }, + "fribidi": { + "description": "Improves drawtext filter", + "dependencies": [ + "fribidi" + ] + }, + "gpl": { + "description": "Allow use of GPL code, the resulting libs and binaries will be under GPL" + }, + "iconv": { + "description": "Iconv support", + "dependencies": [ + "libiconv" + ] + }, + "ilbc": { + "description": "iLBC de/encoding via libilbc", + "dependencies": [ + "libilbc" + ] + }, + "lzma": { + "description": "lzma support", + "dependencies": [ + "liblzma" + ] + }, + "modplug": { + "description": "ModPlug via libmodplug", + "dependencies": [ + "libmodplug" + ] + }, + "mp3lame": { + "description": "MP3 encoding via libmp3lame", + "dependencies": [ + "mp3lame" + ] + }, + "nonfree": { + "description": "Allow use of nonfree code, the resulting libs and binaries will be unredistributable" + }, + "nvcodec": { + "description": "Nvidia video decoding/encoding acceleration", + "supports": "linux | (!osx & !uwp & !(arm64 & windows))", + "dependencies": [ + "ffnvcodec" + ] + }, + "opencl": { + "description": "OpenCL processing", + "supports": "!uwp", + "dependencies": [ + "opencl" + ] + }, + "opengl": { + "description": "OpenGL rendering", + "supports": "!uwp", + "dependencies": [ + "opengl", + "opengl-registry" + ] + }, + "openh264": { + "description": "H.264 de/encoding via openh264", + "dependencies": [ + "openh264" + ] + }, + "openjpeg": { + "description": "JPEG 2000 de/encoding via OpenJPEG", + "dependencies": [ + "openjpeg" + ] + }, + "openmpt": { + "description": "Decoding tracked files via libopenmpt", + "dependencies": [ + "libopenmpt" + ] + }, + "openssl": { + "description": "Needed for https support if gnutls, libtls or mbedtls is not used", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "version3" + ] + }, + "openssl" + ] + }, + "opus": { + "description": "Opus de/encoding via libopus", + "dependencies": [ + "opus" + ] + }, + "postproc": { + "description": "Build the postproc library", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "gpl" + ] + } + ] + }, + "qsv": { + "description": "Intel QSV Codec", + "dependencies": [ + "mfx-dispatch" + ] + }, + "rubberband": { + "description": "Enable rubberband support", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "gpl" + ] + }, + "rubberband" + ] + }, + "sdl2": { + "description": "Sdl2 support", + "dependencies": [ + { + "name": "sdl2", + "default-features": false, + "features": [ + "x11" + ], + "platform": "linux" + }, + { + "name": "sdl2", + "platform": "!linux" + } + ] + }, + "snappy": { + "description": "Snappy compression, needed for hap encoding", + "dependencies": [ + "snappy" + ] + }, + "soxr": { + "description": "Include libsoxr resampling", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "swresample" + ] + }, + "soxr" + ] + }, + "speex": { + "description": "Speex de/encoding via libspeex", + "dependencies": [ + "speex" + ] + }, + "srt": { + "description": "Haivision SRT protocol", + "supports": "!uwp", + "dependencies": [ + "libsrt" + ] + }, + "ssh": { + "description": "SFTP protocol via libssh", + "dependencies": [ + "libssh" + ] + }, + "swresample": { + "description": "Build the swresample library" + }, + "swscale": { + "description": "Build the swscale library" + }, + "tensorflow": { + "description": "TensorFlow as a DNN module backend for DNN based filters like sr", + "supports": "!static", + "dependencies": [ + "tensorflow" + ] + }, + "tesseract": { + "description": "Tesseract, needed for ocr filter", + "supports": "!static", + "dependencies": [ + "tesseract" + ] + }, + "theora": { + "description": "Theora encoding via libtheora", + "dependencies": [ + "libtheora" + ] + }, + "vaapi": { + "description": "VAAPI video decoding/encoding acceleration", + "supports": "linux", + "dependencies": [ + { + "name": "libva", + "default-features": false, + "platform": "linux" + } + ] + }, + "version3": { + "description": "Upgrade (L)GPL to version 3" + }, + "vorbis": { + "description": "Vorbis en/decoding via libvorbis, native implementation exists", + "dependencies": [ + "libvorbis" + ] + }, + "vpx": { + "description": "VP8 and VP9 de/encoding via libvpx", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "avcodec" + ] + }, + "libvpx" + ] + }, + "vulkan": { + "description": "H.264, HEVC and AV1 de/encoding via Vulkan", + "dependencies": [ + "vulkan-headers" + ] + }, + "webp": { + "description": "WebP encoding via libwebp", + "dependencies": [ + "libwebp" + ] + }, + "x264": { + "description": "H.264 encoding via x264", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "gpl" + ] + }, + "x264" + ] + }, + "x265": { + "description": "HEVC encoding via x265", + "dependencies": [ + { + "name": "ffmpeg", + "default-features": false, + "features": [ + "gpl" + ] + }, + "x265" + ] + }, + "xml2": { + "description": "XML parsing using the C library libxml2, needed for dash demuxing support", + "dependencies": [ + "libxml2" + ] + }, + "zlib": { + "description": "zlib support", + "dependencies": [ + "zlib" + ] + }, + "zmq": { + "description": "Enable ZeroMQ support", + "dependencies": [ + "zeromq" + ] + } + } +} diff --git a/profiles/sg0/charset.lua b/profiles/sg0/charset.lua new file mode 100644 index 000000000..40ae09b26 --- /dev/null +++ b/profiles/sg0/charset.lua @@ -0,0 +1,51 @@ +-- Plain-text labels (save slots, menus) map characters through CharacterToSc3. +-- SG0's font has the glyph order of CHAOS;CHILD Love Chu Chu's charset (its script +-- strings decode with it), so build the map from that charset and keep the flags below. +include(root.BasePaths.RootProfilesDir .. '/common/charset.lua'); +include(root.BasePaths.RootProfilesDir .. '/cclcc/charset.lua'); +local characterToSc3 = root.Charset.CharacterToSc3; + +root.Charset = { + Flags = {}, + CharacterToSc3 = characterToSc3 +}; + +for i = 0, (64 * 117) - 1 do root.Charset.Flags[i] = 0; end + +local spaces = {[0]=0, 63}; +for i = 0, #spaces do + root.Charset.Flags[spaces[i]] = root.Charset.Flags[spaces[i]] | CharacterTypeFlags.Space; +end + +-- 、 。 . , ? ! 〜 ” ー ) 〕 ] } 〉 》 」 』 】☆ ★ ♪ 々 ぁ ぃ ぅ ぇ +-- ぉ っ ゃ ゅ ょ ァ ィ ゥ ェ ォ ッ ャ ュ ョ – +local wordEndingPuncts = { + [0]=0x00BE, 0x00BF, 0x00C1, + 0x00C0, 0x00C4, 0x00C5, + 0x00E4, 0x00CB, 0x00E5, + 0x00CD, 0x00CF, 0x00D1, + 0x00D3, 0x00D5, 0x00D7, + 0x00D9, 0x00DB, 0x00DD, + 0x01A5, 0x01A6, 0x00E6, + 0x0187, 0x00E8, 0x00E9, + 0x00EA, 0x00EB, 0x00EC, + 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F2, 0x00F3, + 0x00F4, 0x00F5, 0x00F6, + 0x00F7, 0x00F8, 0x00F9, + 0x00FA, 0x0113 +}; +for i = 0, #wordEndingPuncts do + root.Charset.Flags[wordEndingPuncts[i]] = root.Charset.Flags[wordEndingPuncts[i]] | CharacterTypeFlags.WordEndingPunct; +end + +-- space(63) space(0) “ ( 〔 [ { 〈 《 「 +local wordStartingPuncts = { + [0]=63, 0, 0x00CA, + 0x00CC, 0x00CE, 0x00D0, + 0x00D2, 0x00D4, 0x00D6, + 0x00D8, 0x00DA, 0x00DC +}; +for i = 0, #wordStartingPuncts do + root.Charset.Flags[wordStartingPuncts[i]] = root.Charset.Flags[wordStartingPuncts[i]] | CharacterTypeFlags.WordStartingPunct; +end \ No newline at end of file diff --git a/profiles/sg0/dialogue.lua b/profiles/sg0/dialogue.lua new file mode 100644 index 000000000..57c02af78 --- /dev/null +++ b/profiles/sg0/dialogue.lua @@ -0,0 +1,178 @@ +root.Sprites["ADVBox"] = { + Sheet = "MesBox", + -- meswin.dds: message window (top border at y 884) + Bounds = { X = 0, Y = 884, Width = 1920, Height = 328 } +}; + +root.Sprites["ADVBoxMask"] = { + Sheet = "MesBox", + -- meswin.dds: window reveal mask gradient below it + Bounds = { X = 0, Y = 1212, Width = 1920, Height = 328 } +}; + +root.Sprites["WaitIconSprite"] = { + Sheet = "Data", + Bounds = { X = 1, Y = 456, Width = 112, Height = 64 }, +}; + +root.Dialogue = { + TipsMessageModeIdx = 7, + + REVMessageModeIdx = 9, + REVColor = 0, + REVNameColor = 61, -- Unsure + REVOutlineMode = 1, + REVNameOutlineMode = 1, + ADVBoxSprite = "ADVBox", + ADVBoxMask = "ADVBoxMask", + ADVBoxEffectDuration = 10, + ADVBoxPos = { X = 0, Y = 752 }, + FadeOutDuration = 0.33, + FadeInDuration = 0.33, + TextFadeInDuration = 0.33, + TextFadeOutDuration = 0.33, + DialogueBoxCurrentType = DialogueBoxType.CC, + NVLBoxMaxOpacity = 0.55, + + -- SG0 shows the speaker name as text; no per-character name plate images. + NametagCurrentType = NametagType.None, + + NametagMainSprites = {}, + NametagLabelSprites = {}, + + NametagMainPos = { X = -1, Y = 764 }, + NametagLabelPos = { X = 0, Y = 988 }, + + NametagShowDuration = 16 / 60; + + WaitIconCurrentType = WaitIconType.None, + + AutoIconCurrentType = AutoIconType.None, -- SG0 AUTO ring frames are in meswin.dds; not mapped yet + AutoIconSpriteAnim = "AutoIconSpriteAnim", + AutoIconOffset = { X = 1570, Y = 696 }, + SkipIconCurrentType = SkipIconType.None, -- SG0 SKIP ring frames are in meswin.dds; not mapped yet + SkipIconSpriteAnim = "SkipIconSpriteAnim", + SkipIconOffset = { X = 1712, Y = 702 }, + DialogueFont = "Default", + SetFontSizeRatio = 800.0, + DefaultFontSize = 48, + ColorTable = { + {0xFFFFFF, 0x000000}, {0x5080FF, 0x000000}, + {0xFF7080, 0x000000}, {0xFFA0F8, 0x000000}, + {0x46FF80, 0x000000}, {0x90FFFF, 0x000000}, + {0xFFFF70, 0x000000}, {0x80FFC0, 0x000000}, + {0xFFB080, 0x000000}, {0xB080FF, 0x000000}, + {0x000000, 0x808080}, {0x000000, 0x5080FF}, + {0x000000, 0xFF7080}, {0x000000, 0xFFA0F8}, + {0x000000, 0x268840}, {0x000000, 0x409999}, + {0x000000, 0x888830}, {0x000000, 0x80FFC0}, + {0x000000, 0xFFB080}, {0x000000, 0xB080FF}, + {0xD0D0D0, 0x000000}, {0xD0D0FF, 0x000000}, + {0xFFD0D0, 0x000000}, {0xFFD0FF, 0x000000}, + {0xD0FFD0, 0x000000}, {0xD0FFFF, 0x000000}, + {0xFFFFD0, 0x000000}, {0xE8FFD0, 0x000000}, + {0xFFE8D0, 0x000000}, {0xD0E8FF, 0x000000}, + {0xFFFFFF, 0x808080}, {0xFFFFFF, 0x5080FF}, + {0xFFFFFF, 0xFF7080}, {0xFFFFFF, 0xFFA0F8}, + {0xFFFFFF, 0x46FF80}, {0xFFFFFF, 0x90FFFF}, + {0xFFFFFF, 0xFFFF70}, {0xFFFFFF, 0x80FFC0}, + {0xFFFFFF, 0xFFB080}, {0xFFFFFF, 0xB080FF}, + {0xFFEEEE, 0x000000}, {0xFFCCCC, 0x000000}, + {0xFFAAAA, 0x000000}, {0xFF9999, 0x000000}, + {0xFF8888, 0x000000}, {0xFFFF00, 0x000000}, + {0xFEF000, 0x000000}, {0xFF7777, 0x000000}, + {0xFF6666, 0x000000}, {0xFF5555, 0x000000}, + {0xFF4444, 0x000000}, {0xFF3333, 0x000000}, + {0xFF2222, 0x000000}, {0xFF0000, 0x000000}, + {0xDD0000, 0x000000}, {0xBB0000, 0x000000}, + {0xB00000, 0x000000}, {0xAA0000, 0x000000}, + {0x950000, 0x000000}, {0x808080, 0x000000}, + {0xAAAAAA, 0x000000}, {0xAAC1C9, 0x000000}, + {0x000000, 0x000000}, {0x000000, 0x000000}, + {0x000000, 0x000000}, {0x000000, 0x000000}, + {0x000000, 0x000000}, {0xF80B0B, 0x000000}, + {0xF8910B, 0x000000}, {0x33F12A, 0x000000} + }, + MaxPageSize = 2000, + PageCount = 3, + ColorTagIsUint8 = false, +}; + +MakeAnimation({ + Name = "SkipIconSpriteAnim", + Sheet = "Data", + FirstFrameX = 0, + FirstFrameY = 812, + FrameWidth = 160, + ColWidth = 160, + FrameHeight = 154, + RowHeight = 154, + Frames = 9, + Duration = 0.7, + Rows = 1, + Columns = 9, + PrimaryDirection = AnimationDirections.Right +}); + +MakeAnimation({ + Name = "AutoIconSpriteAnim", + Sheet = "Data", + FirstFrameX = 0, + FirstFrameY = 966, + FrameWidth = 160, + ColWidth = 160, + FrameHeight = 154, + RowHeight = 154, + Frames = 9, + Duration = 0.7, + Rows = 1, + Columns = 9, + PrimaryDirection = AnimationDirections.Right +}); + +local nametagMainX = 0; +local nametagMainY = 0; +local nametagLabelX = 3600; +local nametagLabelY = 0; +local nametagMainWidth = 448; +local nametagMainHeight = 218; +local nametagLabelWidth = 240; +local nametagLabelHeight = 40; + +for i = 1, 80 do + root.Sprites["NametagMainSprite" .. i] = { + Sheet = "NamePlate", + Bounds = { + X = nametagMainX, + Y = nametagMainY, + Width = nametagMainWidth, + Height = nametagMainHeight + } + }; + root.Dialogue.NametagMainSprites[#root.Dialogue.NametagMainSprites + 1] = "NametagMainSprite" .. i; + + if i % 8 == 0 then + nametagMainY = nametagMainY + nametagMainHeight; + nametagMainX = 0; + else + nametagMainX = nametagMainX + nametagMainWidth; + end + + root.Sprites["NametagLabelSprite" .. i] = { + Sheet = "NamePlate", + Bounds = { + X = nametagLabelX, + Y = nametagLabelY, + Width = nametagLabelWidth, + Height = nametagLabelHeight + } + }; + root.Dialogue.NametagLabelSprites[#root.Dialogue.NametagLabelSprites + 1] = "NametagLabelSprite" .. i; + + if i % 2 == 0 then + nametagLabelX = 3600; + nametagLabelY = nametagLabelY + nametagLabelHeight; + else + nametagLabelX = nametagLabelX + nametagLabelWidth; + end +end diff --git a/profiles/sg0/font.lua b/profiles/sg0/font.lua new file mode 100644 index 000000000..31869a796 --- /dev/null +++ b/profiles/sg0/font.lua @@ -0,0 +1,60 @@ +root.Fonts = { + ["Default"] = { + Type = FontType.SingleSheet, + Sheet = "Font", + -- 48x48 cells: font_a.dds rows 0-84, font_b.dds rows 85-124. + GridSize = { X = 64, Y = 125 }, + AdvanceWidthsTable = {}, + }, + + ["LanguageBarrier"] = { + Type = FontType.LanguageBarrier, + ForegroundSheet = "FontLBForeground", + OutlineSheet = "FontLBOutline", + ForegroundGridSize = { X = 64, Y = 125 }, + AdvanceWidthsBinary = { + Path = "resources/cc/font-lb/widths.bin", + BytesPerGlyph = 1, + EmWidth = 48, + }, + OutlineOffset = { X = -4, Y = -4 }, + } +}; + +root.SpriteSheets["FontLBForeground"] = { + Path = "resources/cc/font-lb/foreground.png", + DesignWidth = 3072, + DesignHeight = 6000 +}; + +root.SpriteSheets["FontLBOutline"] = { + Path = "resources/cc/font-lb/outline.png", + DesignWidth = 3648, + DesignHeight = 7128 +}; + +-- Glyph rows 0-6 (half- and full-width Latin, digits, punctuation, #&@) of the SG0 +-- Steam atlas: {left bearing, advance} in atlas pixels, measured from font_a.dds ink with +-- scripts/font_metrics.py --rows 0-6 (gap 3 px, empty cells 15 px). Other glyphs (kana, +-- kanji) keep the full 48 px cell. +local glyphMetrics = { + [1]={0,15}, [2]={0,25}, [3]={5,14}, [4]={0,24}, [5]={0,24}, [6]={0,26}, [7]={0,24}, [8]={0,25}, [9]={0,25}, [10]={0,25}, [11]={0,24}, [12]={0,27}, [13]={0,26}, [14]={0,28}, [15]={0,28}, [16]={2,23}, [17]={2,22}, [18]={0,27}, [19]={0,28}, [20]={7,10}, [21]={1,21}, [22]={0,27}, [23]={1,23}, [24]={0,28}, [25]={0,27}, [26]={0,27}, [27]={0,25}, [28]={0,27}, [29]={0,27}, [30]={0,24}, [31]={0,27}, [32]={0,27}, [33]={0,27}, [34]={0,48}, [35]={0,26}, [36]={0,27}, [37]={0,26}, [38]={1,25}, [39]={0,28}, [40]={0,27}, [41]={0,28}, [42]={0,28}, [43]={3,21}, [44]={0,28}, [45]={1,25}, [46]={8,11}, [47]={4,15}, [48]={0,26}, [49]={8,11}, [50]={0,28}, [51]={1,26}, [52]={0,27}, [53]={0,28}, [54]={0,29}, [55]={6,19}, [56]={1,25}, [57]={3,21}, [58]={0,26}, [59]={0,28}, [60]={0,28}, [61]={0,28}, [62]={0,27}, [63]={1,25}, [64]={0,15}, + [65]={0,27}, [66]={7,11}, [67]={1,21}, [68]={8,11}, [69]={8,11}, [70]={0,26}, [71]={8,11}, [72]={8,11}, [73]={0,27}, [74]={0,27}, [75]={0,26}, [76]={1,25}, [77]={3,20}, [78]={0,26}, [79]={5,16}, [80]={5,17}, [81]={6,16}, [82]={6,13}, [83]={0,27}, [84]={1,25}, [85]={0,28}, [86]={1,25}, [87]={1,24}, [88]={6,15}, [89]={0,26}, [90]={4,20}, [91]={0,25}, [92]={7,12}, [93]={6,13}, [94]={4,15}, [95]={1,23}, [96]={6,16}, [97]={0,29}, [98]={3,18}, [99]={8,9}, [100]={6,18}, [101]={0,15}, [102]={0,15}, [103]={0,15}, [104]={0,15}, [105]={0,15}, [106]={0,15}, [107]={0,15}, [108]={0,15}, [109]={0,15}, [110]={0,15}, [111]={0,15}, [112]={0,15}, [113]={0,15}, [114]={0,15}, [115]={0,15}, [116]={0,15}, [117]={0,15}, [118]={0,15}, [119]={0,15}, [120]={0,15}, [121]={0,15}, [122]={0,15}, [123]={0,15}, [124]={0,15}, [125]={0,15}, [126]={0,15}, [127]={0,15}, [128]={0,15}, + [129]={2,31}, [130]={2,17}, [131]={2,29}, [132]={2,29}, [133]={2,31}, [134]={2,29}, [135]={2,29}, [136]={2,29}, [137]={2,31}, [138]={2,29}, [139]={1,39}, [140]={2,30}, [141]={2,33}, [142]={2,34}, [143]={2,29}, [144]={2,27}, [145]={2,35}, [146]={2,31}, [147]={2,11}, [148]={2,25}, [149]={2,30}, [150]={2,27}, [151]={2,41}, [152]={2,33}, [153]={2,37}, [154]={2,29}, [155]={2,39}, [156]={2,31}, [157]={1,31}, [158]={2,33}, [159]={2,33}, [160]={2,35}, [161]={1,47}, [162]={1,37}, [163]={2,37}, [164]={2,29}, [165]={2,25}, [166]={1,29}, [167]={2,27}, [168]={2,29}, [169]={2,29}, [170]={2,21}, [171]={2,29}, [172]={3,25}, [173]={2,11}, [174]={2,15}, [175]={2,26}, [176]={2,11}, [177]={2,43}, [178]={3,26}, [179]={2,29}, [180]={2,29}, [181]={1,29}, [182]={2,19}, [183]={2,25}, [184]={2,21}, [185]={2,26}, [186]={2,29}, [187]={1,47}, [188]={2,31}, [189]={3,29}, [190]={2,25}, [191]={0,18}, [192]={0,18}, + [193]={1,11}, [194]={1,11}, [195]={3,11}, [196]={3,11}, [197]={1,31}, [198]={3,11}, [199]={0,17}, [200]={0,15}, [201]={2,11}, [202]={2,11}, [203]={1,19}, [204]={1,18}, [205]={2,19}, [206]={2,19}, [207]={1,19}, [208]={1,19}, [209]={1,18}, [210]={1,19}, [211]={1,17}, [212]={1,18}, [213]={1,23}, [214]={1,22}, [215]={1,25}, [216]={1,25}, [217]={1,19}, [218]={1,19}, [219]={0,23}, [220]={1,22}, [221]={0,19}, [222]={1,18}, [223]={1,39}, [224]={1,39}, [225]={0,19}, [226]={0,18}, [227]={6,15}, [228]={3,44}, [229]={0,48}, [230]={2,46}, [231]={8,34}, [232]={0,48}, [233]={1,37}, [234]={2,35}, [235]={2,30}, [236]={2,37}, [237]={2,37}, [238]={2,37}, [239]={2,39}, [240]={2,35}, [241]={2,35}, [242]={2,39}, [243]={2,37}, [244]={2,34}, [245]={2,33}, [246]={2,37}, [247]={2,37}, [248]={2,37}, [249]={2,39}, [250]={1,39}, [251]={2,31}, [252]={2,33}, [253]={2,35}, [254]={2,38}, [255]={0,48}, [256]={0,48}, + [257]={0,48}, [258]={0,48}, [259]={0,48}, [260]={0,48}, [261]={0,48}, [262]={0,48}, [263]={0,48}, [264]={0,48}, [265]={0,48}, [266]={0,48}, [267]={3,29}, [268]={2,19}, [269]={1,26}, [270]={2,15}, [271]={2,26}, [272]={2,29}, [273]={0,48}, [274]={0,29}, [275]={3,45}, [276]={0,48}, [277]={0,48}, [278]={0,48}, [279]={1,47}, [280]={11,28}, [281]={0,48}, [282]={0,48}, [283]={0,48}, [284]={0,48}, [285]={0,48}, [286]={0,48}, [287]={0,48}, [288]={0,48}, [289]={0,48}, [290]={0,48}, [291]={0,48}, [292]={0,48}, [293]={0,48}, [294]={0,48}, [295]={0,48}, [296]={0,48}, [297]={0,48}, [298]={0,48}, [299]={0,48}, [300]={0,48}, [301]={0,48}, [302]={0,48}, [303]={0,48}, [304]={0,48}, [305]={0,15}, [306]={2,29}, [307]={2,28}, [308]={0,48}, [309]={0,48}, [310]={0,48}, [311]={0,48}, [312]={0,48}, [313]={0,48}, [314]={0,48}, [315]={0,48}, [316]={0,48}, [317]={0,48}, [318]={0,48}, [319]={0,48}, [320]={0,48}, + [321]={0,48}, [322]={0,48}, [323]={0,48}, [324]={0,48}, [325]={0,48}, [326]={0,48}, [327]={0,48}, [328]={0,48}, [329]={0,48}, [330]={0,48}, [331]={0,48}, [332]={0,48}, [333]={0,48}, [334]={0,48}, [335]={0,48}, [336]={0,48}, [337]={0,48}, [338]={0,48}, [339]={0,48}, [340]={0,48}, [341]={0,48}, [342]={0,48}, [343]={0,48}, [344]={0,48}, [345]={0,48}, [346]={0,48}, [347]={0,48}, [348]={0,48}, [349]={0,48}, [350]={0,48}, [351]={0,48}, [352]={10,31}, [353]={9,33}, [354]={12,27}, [355]={11,30}, [356]={8,35}, [357]={12,27}, [358]={11,29}, [359]={8,34}, [360]={11,29}, [361]={12,27}, [362]={8,34}, [363]={10,31}, [364]={7,37}, [365]={6,39}, [366]={6,39}, [367]={0,48}, [368]={0,48}, [369]={16,19}, [370]={16,19}, [371]={0,48}, [372]={21,9}, [373]={0,48}, [374]={7,37}, [375]={15,21}, [376]={9,33}, [377]={11,28}, [378]={13,24}, [379]={11,29}, [380]={13,25}, [381]={10,31}, [382]={9,34}, [383]={11,29}, [384]={11,29}, + [385]={7,36}, [386]={12,26}, [387]={12,31}, [388]={12,28}, [389]={12,31}, [390]={13,25}, [391]={1,47}, [392]={6,37}, [393]={5,41}, [394]={2,46}, [395]={1,47}, [396]={4,43}, [397]={4,43}, [398]={4,43}, [399]={7,37}, [400]={4,43}, [401]={4,43}, [402]={4,43}, [403]={7,37}, [404]={7,37}, [405]={4,43}, [406]={4,43}, [407]={0,48}, [408]={6,39}, [409]={5,41}, [410]={7,37}, [411]={1,45}, [412]={8,35}, [413]={11,29}, [414]={12,27}, [415]={11,30}, [416]={3,45}, [417]={9,33}, [418]={6,39}, [419]={9,33}, [420]={5,41}, [421]={13,25}, [422]={2,46}, [423]={2,46}, [424]={1,47}, [425]={1,47}, [426]={1,47}, [427]={1,47}, [428]={0,48}, [429]={5,41}, [430]={5,41}, [431]={2,46}, [432]={2,46}, [433]={2,46}, [434]={2,46}, [435]={3,45}, [436]={4,43}, [437]={1,47}, [438]={1,47}, [439]={13,25}, [440]={13,25}, [441]={8,35}, [442]={4,43}, [443]={3,44}, [444]={4,43}, [445]={4,43}, [446]={4,43}, [447]={4,43}, [448]={8,35}, +} + +local font = root.Fonts["Default"]; +font.LeftBearingsTable = {}; +for i = 1, (64 * 125) do + local metric = glyphMetrics[i]; + if metric then + font.LeftBearingsTable[i] = metric[1]; + font.AdvanceWidthsTable[i] = metric[2]; + else + font.AdvanceWidthsTable[i] = 48; + end +end diff --git a/profiles/sg0/game.lua b/profiles/sg0/game.lua new file mode 100644 index 000000000..ef1c6862c --- /dev/null +++ b/profiles/sg0/game.lua @@ -0,0 +1,77 @@ + + +root.LayerCount = 100; +root.GameFeatures = GameFeature.Sc3VirtualMachine | GameFeature.Renderer2D | GameFeature.Input | GameFeature.Audio | GameFeature.Video; +root.DesignWidth = 1920; +root.DesignHeight = 1080; + +root.WindowName = "STEINS;GATE 0"; +root.WindowIconPath = "resources/cc/icondata/icon.png"; +root.CursorArrowPath = "resources/cc/icondata/cursor_arrow.png"; +root.CursorPointerPath = "resources/cc/icondata/cursor_pointer.png"; +root.UseMoviePriority = true; +root.UseWaveEffects = true; + +root.CharaIsMvl = false; +root.LayFileBigEndian = false; +root.LayFileTexXMultiplier = 1; +root.LayFileTexYMultiplier = 1; + +root.ScreenCaptureCount = 2; + +root.Vm = { + StartScript = 6, -- _Startup_win.scx in SG0 Steam script.mpk + StartScriptBuffer = 0, + GameInstructionSet = InstructionSet.SG0, + UseReturnIds = true, + + ScrWorkChaStructSize = 40, + ScrWorkChaOffsetStructSize = 10, + ScrWorkBgStructSize = 40, + ScrWorkBgOffsetStructSize = 10, + ScrWorkCaptureStructSize = 20, + ScrWorkCaptureOffsetStructSize = 10, + ScrWorkCaptureEffectInfoStructSize = 3, + ScrWorkBgEffStructSize = 30, + ScrWorkBgEffOffsetStructSize = 20, + + MaxLinkedBgBuffers = 2 +}; + +root.PlatformId = 131072; + +include(root.BasePaths.RootProfilesDir .. '/common/scriptinput.lua'); +-- SG0 PC keys per the game's own help page (manual.mpk help00): +-- the phone ("RINE trigger", script key 22, unmapped in common) is Z or C, +-- i.e. L2 / R2; F5 is Load and F7 Quick Load (common has them the other way). +root.Input.PADcustomA[23] = root.PADinput.PAD1L2 | root.PADinput.PAD1R2; +root.Input.PADcustomB[23] = root.PADinput.PAD1L2 | root.PADinput.PAD1R2; +root.Input.KBcustom[55] = {KeyboardScanCode._F7}; +root.Input.KBcustom[58] = {KeyboardScanCode._F5}; +include(root.BasePaths.RootProfilesDir .. '/common/scriptvars.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/scriptvars.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/savedata.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/tipssystem.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/vfs.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/sprites.lua'); +include(root.BasePaths.RootProfilesDir .. '/common/animation.lua'); +include(root.BasePaths.RootProfilesDir .. '/common/achievementnotification.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/charset.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/font.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/dialogue.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/waveeffects.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/saveicon.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/loadingdisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/datedisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/titlemenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/savemenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/phone.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/systemmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/helpmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/backlogmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/sysmesboxdisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/selectiondisplay.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/tipsmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/optionsmenu.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/tipsnotification.lua'); +include(root.BasePaths.RootProfilesDir .. '/sg0/hud/extramenus.lua'); diff --git a/profiles/sg0/hud/backlogmenu.lua b/profiles/sg0/hud/backlogmenu.lua new file mode 100644 index 000000000..d2beacad0 --- /dev/null +++ b/profiles/sg0/hud/backlogmenu.lua @@ -0,0 +1,45 @@ +-- STEINS;GATE 0 backlog (backlog.dds): the purple panel centred on a dimmed +-- scene, entries clipped to it, SG0's scrollbar and voice-replay icon. +-- Text layout (names in text, 1386 px lines) comes from the scripts' mode 9. +local sheet = "Backlog"; + +local function sprite(name, x, y, w, h) + root.Sprites[name] = { Sheet = sheet, Bounds = { X = x, Y = y, Width = w, Height = h } }; + return name; +end + +root.BacklogMenu = { + Type = BacklogMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + + BacklogBackgroundSprite = sprite("BacklogPanel", 160, 0, 1668, 1080), + PanelPos = { X = 126, Y = 0 }, + ShadeOpacity = 0.6, + + EntryHighlightLocation = EntryHighlightLocationType.None, + EntryHighlightSprite = "BacklogPanel", + EntryHighlightOffset = { X = 0, Y = 0 }, + EntryHighlightPadding = 8.0, + HighlightBounds = { X = 176, Y = 0, Width = 1568, Height = 0 }, + HighlightColor = 0xFFA040, + HighlightOpacity = 0.16, + + VoiceIconSprite = sprite("BacklogVoiceIcon", 1928, 116, 60, 48), + VoiceIconOffset = { X = -14, Y = 2 }, + + ScrollbarTrackSprite = sprite("BacklogScrollbarTrack", 1924, 1084, 28, 1084), + ScrollbarThumbSprite = sprite("BacklogScrollbarThumb", 1932, 8, 28, 92), + ScrollbarPosition = { X = 1808, Y = -2 }, + ScrollbarThumbLength = 92, + + -- Entries are offset by text mode 9's window position (243 px), so text starts at x 300. + EntriesStart = { X = 57, Y = 60 }, + RenderingBounds = { X = 126, Y = 40, Width = 1668, Height = 1000 }, + HoverBounds = { X = 230, Y = 40, Width = 1500, Height = 1000 }, + EntryYPadding = 24, + + FadeInDuration = 16 / 60, + FadeOutDuration = 16 / 60, + ScrollingSpeed = 900, + PageUpDownHeight = 900 +}; diff --git a/profiles/sg0/hud/datedisplay.lua b/profiles/sg0/hud/datedisplay.lua new file mode 100644 index 000000000..940299136 --- /dev/null +++ b/profiles/sg0/hud/datedisplay.lua @@ -0,0 +1,178 @@ +local sheet = "Data"; +local name = "Date"; + +local yearNumFirstX = 2287.5; +local yearNumFirstY = 94.5; +local yearNumWidth = 37.5; +local yearNum1Width = 12; +local yearNumHeight = 30; + +local numFirstX = 2286; +local numFirstY = 49.5; +local numWidth = 60; +local num1Width = 18; +local numHeight = 42; + +local weekFirstX = 2677.5; +local weekFirstY = 94.5; +local weekSecondX = 2287.5; +local weekSecondY = 127.5; +local weekWidth = 109.5; +local weekFriWidth = 84; +local weekHeight = 30; + +root.DateDisplay = { + Type = DateDisplayType.RNE, + YearNumSprites = {}, + MonthNumSprites = {}, + DayNumSprites = {}, + WeekSprites = {}, + BackgroundStartPos = { X = 1632, Y = 109.5 }, + BackgroundEndPos = { X = 1632 - 384, Y = 109.5 }, + DateStartX = 1750.5, + YearWeekY = 90, + MonthDayY = 78, + Spacing = 1.5, + FadeInDuration = 0.5, + FadeOutDuration = 0.5 +}; + +for i = 0, 9 do + local yearW; + if i == 1 then + yearW = yearNum1Width; + else + yearW = yearNumWidth; + end + + root.Sprites[name .. "YearNum" .. i] = { + Sheet = sheet, + Bounds = { + X = yearNumFirstX, + Y = yearNumFirstY, + Width = yearW, + Height = yearNumHeight + } + }; + root.DateDisplay.YearNumSprites[#root.DateDisplay.YearNumSprites + 1] = name .. "YearNum" .. i; + + local numW + if i == 1 then + numW = num1Width; + else + numW = numWidth; + end + + root.Sprites[name .. "Num" .. i] = { + Sheet = sheet, + Bounds = { + X = numFirstX, + Y = numFirstY, + Width = numW, + Height = numHeight + } + }; + root.DateDisplay.MonthNumSprites[#root.DateDisplay.MonthNumSprites + 1] = name .. "Num" .. i; + root.DateDisplay.DayNumSprites[#root.DateDisplay.DayNumSprites + 1] = name .. "Num" .. i; + + if i == 1 then + yearNumFirstX = yearNumFirstX + yearNum1Width; + numFirstX = numFirstX + num1Width; + else + yearNumFirstX = yearNumFirstX + yearNumWidth; + numFirstX = numFirstX + numWidth; + end +end + +for i = 0, 6 do + if i > 1 then + local weekW; + if i == 5 then + weekW = weekFriWidth; + else + weekW = weekWidth; + end + + root.Sprites[name .. "Week" .. i] = { + Sheet = sheet, + Bounds = { + X = weekSecondX, + Y = weekSecondY, + Width = weekW, + Height = weekHeight + } + }; + if i == 5 then + weekSecondX = weekSecondX + weekFriWidth; + else + weekSecondX = weekSecondX + weekWidth; + end + else + root.Sprites[name .. "Week" .. i] = { + Sheet = sheet, + Bounds = { + X = weekFirstX, + Y = weekFirstY, + Width = weekWidth, + Height = weekHeight + } + }; + weekFirstX = weekFirstX + weekWidth; + end + root.DateDisplay.WeekSprites[#root.DateDisplay.WeekSprites + 1] = name .. "Week" .. i; +end + +root.Sprites[name .. "MonthDaySeparator"] = { + Sheet = sheet, + Bounds = { + X = 2845.5, + Y = 49.5, + Width = 15, + Height = 42 + } +}; +root.DateDisplay.MDSeparatorSprite = name .. "MonthDaySeparator"; + +root.Sprites[name .. "DayYearSeparator"] = { + Sheet = sheet, + Bounds = { + X = 2637, + Y = 94.5, + Width = 12, + Height = 30 + } +}; +root.DateDisplay.DYSeparatorSprite = name .. "DayYearSeparator"; + +root.Sprites[name .. "OpenBracket"] = { + Sheet = sheet, + Bounds = { + X = 2649, + Y = 94.5, + Width = 12, + Height = 30 + } +}; +root.DateDisplay.OpenBracketSprite = name .. "OpenBracket"; + +root.Sprites[name .. "CloseBracket"] = { + Sheet = sheet, + Bounds = { + X = 2664, + Y = 94.5, + Width = 12, + Height = 30 + } +}; +root.DateDisplay.CloseBracketSprite = name .. "CloseBracket"; + +root.Sprites[name .. "Background"] = { + Sheet = sheet, + Bounds = { + X = 2287.5, + Y = 2, + Width = 675, + Height = 42 + } +}; +root.DateDisplay.BackgroundSprite = name .. "Background"; \ No newline at end of file diff --git a/profiles/sg0/hud/extramenus.lua b/profiles/sg0/hud/extramenus.lua new file mode 100644 index 000000000..34f5fa213 --- /dev/null +++ b/profiles/sg0/hud/extramenus.lua @@ -0,0 +1,205 @@ +-- STEINS;GATE 0 EXTRA screens. Sheet rectangles measured with +-- scripts/sheet_regions.py: exmenu.dds (library chrome), exmenu2.dds (the +-- sound library's player window), album_thum.dds (108 CG thumbnails, 288x162 +-- on a 294x168 pitch, 10 per row) and clearlist.dds (Nixie tubes). +root.SpriteSheets["ExMenu"] = { Path = { Mount = "system", Id = 7 }, DesignWidth = 3072, DesignHeight = 3072 }; +root.SpriteSheets["ExMenu2"] = { Path = { Mount = "system", Id = 8 }, DesignWidth = 1920, DesignHeight = 984 }; +root.SpriteSheets["AlbumThumb"] = { Path = { Mount = "system", Id = 0 }, DesignWidth = 2944, DesignHeight = 1856 }; +root.SpriteSheets["ClearList"] = { Path = { Mount = "system", Id = 2 }, DesignWidth = 3072, DesignHeight = 2432 }; + +local function sprite(name, sheet, x, y, w, h) + root.Sprites[name] = { Sheet = sheet, Bounds = { X = x, Y = y, Width = w, Height = h } }; + return name; +end + +local thumbnails = {}; +for i = 0, 107 do + thumbnails[#thumbnails + 1] = sprite("LibraryCgThumb" .. i, "AlbumThumb", + 294 * (i % 10), 168 * math.floor(i / 10), 288, 162); +end + +-- Sound library tracks: _system strings 151.. (title, credit) pairs. Tracks +-- 1-28 are bgm201-228 (loop ids 1, 3, .. 55, no-loop ids 2, 4, .. 56), then +-- bgm229nl, bgm230 and the four vocal songs (song01-04), which are also +-- unlocked by watching the movie they belong to (OP, Ending1-3). +local trackBgm, trackBgmAlt, trackMovie = {}, {}, {}; +for t = 0, 27 do + trackBgm[#trackBgm + 1] = 1 + 2 * t; + trackBgmAlt[#trackBgmAlt + 1] = 2 + 2 * t; + trackMovie[#trackMovie + 1] = -1; +end +for _, track in ipairs({ { 57, -1, -1 }, { 58, 59, -1 }, { 69, -1, 0 }, { 70, -1, 1 }, { 71, -1, 2 }, { 72, -1, 3 } }) do + trackBgm[#trackBgm + 1] = track[1]; + trackBgmAlt[#trackBgmAlt + 1] = track[2]; + trackMovie[#trackMovie + 1] = track[3]; +end + +root.ExtraMenus = { + LibraryMenu = { + Type = LibraryMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + -- _Startup_win counts SW_SYSSUBMENUCT to 32 while a library opens. + CountMax = 32, + + BackgroundSprite = sprite("LibraryBackground", "ExMenu", 0, 0, 1920, 1080), + TabSprites = { + sprite("LibraryTabClearList", "ExMenu", 1928, 0, 416, 64), + sprite("LibraryTabCg", "ExMenu", 1928, 64, 416, 64), + sprite("LibraryTabSound", "ExMenu", 1928, 130, 416, 64), + sprite("LibraryTabMovie", "ExMenu", 1928, 196, 416, 64), + }, + TabSelectedSprites = { + sprite("LibraryTabClearListOn", "ExMenu", 2344, 0, 416, 64), + sprite("LibraryTabCgOn", "ExMenu", 2344, 64, 416, 64), + sprite("LibraryTabSoundOn", "ExMenu", 2344, 130, 416, 64), + sprite("LibraryTabMovieOn", "ExMenu", 2344, 196, 416, 64), + }, + TabPos = { X = 60, Y = 214 }, + TabPitch = 66, + TitleSprites = { + sprite("LibraryTitleCg", "ExMenu", 1928, 268, 228, 52), + sprite("LibraryTitleSound", "ExMenu", 2168, 268, 292, 52), + sprite("LibraryTitleMovie", "ExMenu", 2476, 268, 280, 52), + }, + TitlePos = { X = 60, Y = 130 }, + GuideSprites = { + sprite("LibraryGuideCg", "ExMenu", 1016, 2180, 872, 60), + sprite("LibraryGuideViewer", "ExMenu", 884, 2256, 1004, 64), + sprite("LibraryGuideSound", "ExMenu", 748, 2336, 1140, 60), + sprite("LibraryGuideMovie", "ExMenu", 1016, 2412, 872, 64), + }, + GuideRight = 1888, + GuideY = 1008, + + ThumbnailSprites = thumbnails, + NoDataSprite = sprite("LibraryNoData", "ExMenu", 2772, 172, 296, 172), + NoDataSelectedSprite = sprite("LibraryNoDataOn", "ExMenu", 2772, 696, 296, 168), + SelectedFrameSprite = sprite("LibraryThumbFrame", "ExMenu", 2772, 360, 296, 168), + SelectedGlowSprite = sprite("LibraryThumbGlow", "ExMenu", 2764, 0, 292, 164), + CgGridPos = { X = 520, Y = 112 }, + CgCellPitch = { X = 334, Y = 202 }, + CgColumns = 4, + CgRows = 4, + ThumbnailOffset = { X = 4, Y = 5 }, + ScrollThumbSprite = sprite("LibraryScrollThumb", "ExMenu", 2636, 332, 28, 72), + ScrollX = 1846, + ScrollTop = 112, + ScrollBottom = 890, + -- EV_SP01-04 have no EV flag (see savedata.lua AlbumEvData). + AllClearCgs = { 102, 103, 104, 105 }, + EndingFlags = { 801, 802, 803, 804, 805, 806 }, + -- Script BG buffer (bit) the loader thread fills: 4 = BG3, unused on the title. + CgLoadBuffer = 4, + CgZoomMax = 2.0, + CgScrollSpeed = 900, + + PlayerSprite = sprite("LibraryPlayer", "ExMenu2", 4, 4, 1804, 972), + PlayerPos = { X = 58, Y = 14 }, + ListHeaderSprite = sprite("LibraryListHeader", "ExMenu", 0, 2712, 1348, 44), + ListRowSprites = { + sprite("LibraryListRowA", "ExMenu", 0, 2763, 1348, 63), + sprite("LibraryListRowB", "ExMenu", 0, 2826, 1348, 63), + }, + ListSelectedSprite = sprite("LibraryListRowOn", "ExMenu", 0, 2645, 1348, 62), + -- The player window's white list pane (exmenu2 x 422-1765, y 152-919). + ListBounds = { X = 476, Y = 162, Width = 1344, Height = 768 }, + ListRowHeight = 63, + ListVisibleRows = 11, + ListTrackX = 20, + ListTitleX = 132, + ListArtistX = 612, + ListFontSize = 30, + -- The player's LCD (exmenu2 x 908-1675, y 32-117), right of its icon. + NowPlayingPos = { X = 1080, Y = 50 }, + NowPlayingFontSize = 28, + RepeatModeSprites = { + sprite("LibraryRepeatOne", "ExMenu", 2768, 536, 48, 40), + sprite("LibraryPlayThrough", "ExMenu", 2768, 574, 48, 40), + sprite("LibraryRepeatAll", "ExMenu", 2768, 610, 48, 40), + sprite("LibraryShuffle", "ExMenu", 2768, 646, 48, 40), + }, + RepeatModePos = { X = 1780, Y = 938 }, + PlayIconSprite = sprite("LibraryPlayIcon", "ExMenu", 2680, 336, 76, 76), + TrackStringBase = 151, + LockedTrackString = 148, + TrackBgmIds = trackBgm, + TrackBgmAltIds = trackBgmAlt, + TrackMovieUnlocks = trackMovie, + + -- Opening, Ending1-3, Animelo Summer Live 2015 (movie.cls ids 16, 0, 1, + -- 2, 31; titles are _system strings 135..). + MovieThumbnailSprites = { + sprite("LibraryMovieOp", "ExMenu", 1926, 1800, 480, 272), + sprite("LibraryMovieEd1", "ExMenu", 2412, 1800, 480, 272), + sprite("LibraryMovieEd2", "ExMenu", 1926, 2076, 480, 272), + sprite("LibraryMovieEd3", "ExMenu", 2412, 2076, 480, 272), + sprite("LibraryMovieAnisama", "ExMenu", 1926, 2352, 480, 272), + }, + MovieIds = { 16, 0, 1, 2, 31 }, + MovieGridPos = { X = 540, Y = 200 }, + MovieCellPitch = { X = 430, Y = 380 }, + MovieColumns = 3, + MovieThumbnailScale = 0.8, + MovieTitleOffsetY = 14, + MovieStringBase = 135, + MovieFontSize = 30, + -- {script id, line id, ...}: the line shown just before each place the + -- story plays the movie (Assign W61 = movie, then the movie macro). + MovieUnlockLines = { + { 19, 153, 72, 183 }, + { 27, 248, 33, 223, 119, 187 }, + { 17, 383, 44, 208, 65, 126, 90, 866, 118, 85 }, + { 21, 201, 45, 451, 109, 185 }, + { 54, 347, 75, 49 }, + }, + + TextColor = 0x303030, + LockedTextColor = 0x8C8C8C, + SelectedTextColor = 0xFFFFFF, + -- _system.scx is loaded in script buffer 1 on the title. + SystemScriptBuffer = 1, + }, + + ClearListMenu = { + Type = ClearListMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + -- _Startup_win counts SW_SYSSUBMENUCT to 64 while the clear list opens. + CountMax = 64, + BackgroundSprite = sprite("ClearListBackground", "ClearList", 0, 0, 1920, 1080), + -- Left to right: Kagari, Maho, Kurisu, Okabe, Mayuri, Leskinen; each lit + -- tube is aligned glass-to-glass with the backdrop's tube. + TubeSprites = { + sprite("ClearListTube0", "ClearList", 1928, 4, 280, 924), + sprite("ClearListTube1", "ClearList", 2226, 4, 280, 924), + sprite("ClearListTube2", "ClearList", 2525, 4, 280, 924), + sprite("ClearListTube3", "ClearList", 1931, 940, 280, 924), + sprite("ClearListTube4", "ClearList", 2229, 940, 280, 924), + sprite("ClearListTube5", "ClearList", 2528, 940, 280, 924), + }, + TubePositions = { + { X = 74, Y = 54 }, { X = 372, Y = 54 }, { X = 671, Y = 54 }, + { X = 971, Y = 54 }, { X = 1269, Y = 54 }, { X = 1568, Y = 54 }, + }, + -- Recursive Mother Goose, Twin Automata, Promised Rinascimento, + -- Milky-way Crossing, Vega and Altair, Gehenna's Stigma. + LabelSprites = { + sprite("ClearListLabel0", "ClearList", 8, 2176, 288, 244), + sprite("ClearListLabel1", "ClearList", 312, 2176, 300, 244), + sprite("ClearListLabel2", "ClearList", 628, 2176, 268, 244), + sprite("ClearListLabel3", "ClearList", 916, 2176, 264, 244), + sprite("ClearListLabel4", "ClearList", 1196, 2176, 272, 244), + sprite("ClearListLabel5", "ClearList", 1492, 2176, 268, 244), + }, + -- Over the tube bases, ending above the BACK guide. + LabelPositions = { + { X = 68, Y = 772 }, { X = 360, Y = 772 }, { X = 675, Y = 772 }, + { X = 977, Y = 772 }, { X = 1271, Y = 772 }, { X = 1572, Y = 772 }, + }, + -- CLRFLG.scx sets flag 801 + ending: 0 A04 (Promised Rinascimento), + -- 1 M01 (Twin Automata), 2 K01 (Recursive Mother Goose), 3 B01 (Vega + -- and Altair), 4 B02 (Gehenna's Stigma), 5 T01 (Milky-way Crossing). + EndingFlags = { 803, 802, 801, 806, 804, 805 }, + GuideSprite = sprite("ClearListGuide", "ClearList", 2848, 8, 168, 60), + GuidePos = { X = 1736, Y = 1016 }, + }, +}; diff --git a/profiles/sg0/hud/helpmenu.lua b/profiles/sg0/hud/helpmenu.lua new file mode 100644 index 000000000..e3f395f9d --- /dev/null +++ b/profiles/sg0/hud/helpmenu.lua @@ -0,0 +1,14 @@ +-- STEINS;GATE 0 help: the two operating-instruction pages in manual.mpk +-- (keyboard & mouse, controller), each a full 1920x1080 image. +root.SpriteSheets["HelpPage0"] = { Path = { Mount = "manual", Id = 0 }, DesignWidth = 1920, DesignHeight = 1080 }; +root.SpriteSheets["HelpPage1"] = { Path = { Mount = "manual", Id = 1 }, DesignWidth = 1920, DesignHeight = 1080 }; +root.Sprites["HelpPage0"] = { Sheet = "HelpPage0", Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 } }; +root.Sprites["HelpPage1"] = { Sheet = "HelpPage1", Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 } }; + +root.HelpMenu = { + Type = HelpMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + PageSprites = { "HelpPage0", "HelpPage1" }, + -- _Startup_win counts SW_SYSSUBMENUCT to 32 while the help opens. + CountMax = 32 +}; diff --git a/profiles/sg0/hud/loadingdisplay.lua b/profiles/sg0/hud/loadingdisplay.lua new file mode 100644 index 000000000..f86c4eb55 --- /dev/null +++ b/profiles/sg0/hud/loadingdisplay.lua @@ -0,0 +1,78 @@ +root.LoadingDisplay = { + ResourceLoadBgAnim = "ResourceLoadingBg", + SaveLoadBgAnim = "SaveLoadingBg", + LoadingIconAnim = "LoadingDisc", + LoadingTextAnim = "LoadingText", + ResourceBgPos = { X = 1074, Y = 611 }, + SaveBgPos = { X = 1019, Y = 598 }, + IconPos = { X = 986, Y = 608 }, + TextPos = { X = 1025, Y = 628 }, + FadeInDuration = 0.66, + FadeOutDuration = 0.33 +}; + +MakeAnimation({ + Name = "ResourceLoadingBg", + Sheet = "Data", + FirstFrameX = 555, + FirstFrameY = 544, + FrameWidth = 206, + ColWidth = 206, + FrameHeight = 58, + RowHeight = 60, + Frames = 8, + Duration = 0.8, + Rows = 8, + Columns = 1, + PrimaryDirection = AnimationDirections.Down +}); + +MakeAnimation({ + Name = "SaveLoadingBg", + Sheet = "Data", + FirstFrameX = 388, + FirstFrameY = 0, + FrameWidth = 277, + ColWidth = 279, + FrameHeight = 81, + RowHeight = 83, + Frames = 4, + Duration = 0.8, + Rows = 2, + Columns = 2, + PrimaryDirection = AnimationDirections.Down, + SecondaryDirection = AnimationDirections.Right +}); + +MakeAnimation({ + Name = "LoadingDisc", + Sheet = "Data", + FirstFrameX = 172, + FirstFrameY = 1, + FrameWidth = 60, + ColWidth = 62, + FrameHeight = 60, + RowHeight = 62, + Frames = 3, + Duration = 0.8, + Rows = 1, + Columns = 3, + PrimaryDirection = AnimationDirections.Right +}); +root.Animations["LoadingDisc"].Frames[#root.Animations["LoadingDisc"].Frames + 1] = "LoadingDisc1"; + +MakeAnimation({ + Name = "LoadingText", + Sheet = "Data", + FirstFrameX = 173, + FirstFrameY = 91, + FrameWidth = 214, + ColWidth = 214, + FrameHeight = 21, + RowHeight = 23, + Frames = 5, + Duration = 0.8, + Rows = 5, + Columns = 1, + PrimaryDirection = AnimationDirections.Down +}); \ No newline at end of file diff --git a/profiles/sg0/hud/optionsmenu.lua b/profiles/sg0/hud/optionsmenu.lua new file mode 100644 index 000000000..9dad4f73e --- /dev/null +++ b/profiles/sg0/hud/optionsmenu.lua @@ -0,0 +1,115 @@ +-- STEINS;GATE 0 config (config.dds): BASIC / TEXT / SOUND / VOICE pages. +-- Each row is a label line (with its dotted leader) plus either choice words, +-- drawn at their offset from the label in the sheet, or a MIN-MAX slider. +-- Console-only rows of the sheet (screen size, touchscreen, L/R message send) +-- are left out. +root.SpriteSheets["Config"] = { Path = { Mount = "system", Id = 3 }, DesignWidth = 3072, DesignHeight = 2496 }; + +local function sprite(name, x, y, w, h) + root.Sprites[name] = { Sheet = "Config", Bounds = { X = x, Y = y, Width = w, Height = h } }; + return name; +end + +local on = sprite("ConfigOn", 2788, 1104, 68, 36); +local off = sprite("ConfigOff", 2936, 1104, 96, 36); +local function onOffRow(page, setting, name, y, x, w) + -- Every ON / OFF pair sits at x 2788 / 2936 on its own line. + return { + Page = page, Setting = setting, + Label = sprite(name, x, y - 4, w, 44), + Choices = { sprite(name .. "On", 2788, y, 68, 36), sprite(name .. "Off", 2936, y, 96, 36) }, + }; +end +local function sliderRow(page, setting, name, x, y, w, index) + return { Page = page, Setting = setting, Index = index, Label = sprite(name, x, y, w, 44) }; +end + +local characters = {}; +for i = 0, 15 do + local col, row = i % 4, math.floor(i / 4); + local cellY = ({ 2180, 2256, 2336, 2412 })[row + 1]; + characters[i + 1] = sliderRow(3, "CharacterVoice", "ConfigVoiceChar" .. i, 8 + col * 624, cellY, 608, i); + root.Sprites["ConfigVoiceChar" .. i].Bounds.Height = 64; +end + +local rows = { + -- BASIC + { Page = 0, Setting = "FullScreen", Label = sprite("ConfigFullScreen", 1944, 556, 568, 44), + Choices = { sprite("ConfigFullScreenOn", 2532, 556, 272, 40), sprite("ConfigWindow", 2856, 556, 192, 40) } }, + { Page = 0, Setting = "Resolution", Label = sprite("ConfigResolution", 1944, 628, 528, 44), + Choices = { sprite("ConfigRes1024", 2488, 632, 160, 32), sprite("ConfigRes1280", 2676, 632, 160, 32), + sprite("ConfigRes1920", 2868, 632, 180, 32) } }, + onOffRow(0, "TriggerStopSkip", "ConfigSkipRelease", 1104, 1944, 804), + onOffRow(0, "ShowTipsNotification", "ConfigTipsInfo", 1176, 1940, 808), + { Page = 0, Setting = "AutoQuickSave", Label = sprite("ConfigAutoQuickSave", 1940, 1244, 472, 48), + Choices = { sprite("ConfigAqsScene", 2436, 1248, 120, 36), sprite("ConfigAqsTrigger", 2592, 1248, 168, 36), + sprite("ConfigAqsAll", 2796, 1248, 76, 36), sprite("ConfigAqsOff", 2936, 1248, 96, 36) } }, + -- TEXT + sliderRow(1, "TextSpeed", "ConfigMessageSpeed", 1940, 1460, 608), + sliderRow(1, "AutoSpeed", "ConfigAutoDelay", 1940, 1532, 604), + { Page = 1, Setting = "SkipRead", Label = sprite("ConfigSkipMode", 1944, 1604, 724, 44), + Choices = { sprite("ConfigSkipReadOnly", 2696, 1608, 188, 36), sprite("ConfigSkipAll", 2956, 1608, 76, 36) } }, + -- SOUND + sliderRow(2, "VoiceVolume", "ConfigVoiceVolume", 1940, 1748, 604), + sliderRow(2, "MusicVolume", "ConfigMusicVolume", 1940, 1820, 604), + sliderRow(2, "SEVolume", "ConfigSEVolume", 1944, 1892, 600), + sliderRow(2, "MovieVolume", "ConfigMovieVolume", 1940, 1968, 604), + onOffRow(2, "SyncVoice", "ConfigSyncVoice", 2040, 1944, 804), + onOffRow(2, "SkipVoice", "ConfigSkipVoice", 2112, 1944, 804), +}; +for _, row in ipairs(characters) do rows[#rows + 1] = row; end + +root.OptionsMenu = { + Type = OptionsMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + -- _Startup_win counts SW_SYSSUBMENUCT to 32 while the config opens. + CountMax = 32, + FadeInDuration = 32 / 60, + FadeOutDuration = 32 / 60, + + BackgroundSprite = sprite("ConfigBackground", 0, 0, 1920, 1080), + GuideSprite = "ConfigGuide", + GuidePos = { X = 1000, Y = 1012 }, + VoiceGuideSprite = "ConfigVoiceGuide", + VoiceGuidePos = { X = 600, Y = 1016 }, + + PageButtonSprites = { + sprite("ConfigPageBasic", 1936, 16, 472, 148), sprite("ConfigPageText", 1936, 196, 472, 148), + sprite("ConfigPageSound", 1936, 376, 472, 148), sprite("ConfigPageVoice", 2424, 376, 476, 148), + }, + PageButtonBounds = { + { X = 172, Y = 56, Width = 472, Height = 148 }, { X = 172, Y = 236, Width = 472, Height = 148 }, + { X = 172, Y = 416, Width = 472, Height = 148 }, { X = 172, Y = 596, Width = 476, Height = 148 }, + }, + PageHeadingSprites = { + sprite("ConfigHeadingBasic", 1940, 752, 392, 52), sprite("ConfigHeadingText", 1940, 840, 372, 48), + sprite("ConfigHeadingSound", 1940, 932, 420, 48), sprite("ConfigHeadingVoice", 1940, 1024, 400, 48), + }, + HeadingPos = { X = 724, Y = 70 }, + + ContentX = 720, + RowsTop = 170, + RowHeight = 72, + VoiceRowHeight = 52, + RowPadding = 8, + RowWidth = 1150, + + SliderFrameSprite = sprite("ConfigSliderFrame", 2428, 60, 488, 56), + SliderFillSprite = sprite("ConfigSliderFill", 2436, 132, 476, 44), + SliderKnobSprite = sprite("ConfigSliderKnob", 2932, 56, 100, 80), + SliderOffset = { X = 640, Y = -6 }, + SliderFillOffset = { X = 6, Y = 6 }, + SliderKnobOffsetY = -12, + VoiceSliderScale = 0.66, + VoiceSliderOffsetY = 12, + VoiceOffSprite = sprite("ConfigVoiceOff", 2436, 312, 476, 44), + + HighlightColor = 0xFFA040, + HighlightOpacity = 0.14, + UnselectedBrightness = 0.45, + + Rows = rows +}; + +root.Sprites["ConfigGuide"] = { Sheet = "SysTips", Bounds = { X = 980, Y = 1776, Width = 908, Height = 64 } }; +root.Sprites["ConfigVoiceGuide"] = { Sheet = "SysTips", Bounds = { X = 596, Y = 1856, Width = 1292, Height = 60 } }; diff --git a/profiles/sg0/hud/phone.lua b/profiles/sg0/hud/phone.lua new file mode 100644 index 000000000..2c5f38f75 --- /dev/null +++ b/profiles/sg0/hud/phone.lua @@ -0,0 +1,69 @@ +-- STEINS;GATE 0 phone overlay (phone_b.dds): Rintaro's phone slides in at the +-- right while flag 2600 is set; positions are 1920x1080 design pixels. +root.GameSpecific = { + Type = GameSpecificType.SG0 +}; + +root.SpriteSheets["PhoneB"] = { + Path = { Mount = "system", Id = 18 }, + DesignWidth = 3072, + DesignHeight = 1536 +}; + +root.SpriteSheets["PhoneRine"] = { + Path = { Mount = "system", Id = 30 }, + DesignWidth = 704, + DesignHeight = 1216 +}; + +root.Sprites["PhoneBody"] = { Sheet = "PhoneB", Bounds = { X = 12, Y = 16, Width = 660, Height = 1260 } }; +root.Sprites["PhoneWallpaper"] = { Sheet = "PhoneB", Bounds = { X = 1472, Y = 0, Width = 456, Height = 700 } }; +root.Sprites["PhoneSelection"] = { Sheet = "PhoneB", Bounds = { X = 1932, Y = 480, Width = 426, Height = 60 } }; + +root.Phone = { + DrawType = DrawComponentType.Main, + BodySprite = "PhoneBody", + Scale = 0.75, + ShownPos = { X = 1400, Y = 24 }, + HiddenX = 1940, + -- The screen sits at (104, 104) in the 660x1260 body and is 456x804, scaled by 0.75. + ScreenOffset = { X = 78, Y = 78 }, + ScreenSize = { X = 342, Y = 603 }, + WallpaperSprite = "PhoneWallpaper", + ScreenShadeAlpha = 0.45, + SelectionSprite = "PhoneSelection", + Padding = { X = 20, Y = 24 }, + ItemsTop = 132, + RowHeight = 56, + RowPadding = 10, + TitleFontSize = 32, + ItemFontSize = 30, + TextColor = 0xFFFFFF, + OutlineColor = 0x000000, + + -- RINE: phone_rine.dds holds 12 avatars (92 px, 96 apart) and 21 stickers + -- (192x168 on a 3-column grid at 96 + 198c, 174r). + RineAvatarSprite = "PhoneRineAvatar", + RineAvatarStepY = 96, + RineStickerSprite = "PhoneRineSticker", + RineStickerStep = { X = 198, Y = 174 }, + RineStickerColumns = 3, + RineTop = 70, + RineMargin = 8, + RineGap = 8, + RineAvatarSize = 36, + RineStickerSize = { X = 96, Y = 84 }, + RineFontSize = 20, + RineLineHeight = 22, + RineBubbleMaxWidth = 210, + RineBubblePadding = { X = 8, Y = 6 }, + RineOwnColor = 0x9EDC70, + RineOtherColor = 0xFFFFFF, + RineTextColor = 0x1E1E1E, + RineReplyRowHeight = 34, + RineReplyStickerSize = { X = 96, Y = 84 }, + RineInputHeight = 40 +}; + +root.Sprites["PhoneRineAvatar"] = { Sheet = "PhoneRine", Bounds = { X = 0, Y = 0, Width = 92, Height = 92 } }; +root.Sprites["PhoneRineSticker"] = { Sheet = "PhoneRine", Bounds = { X = 96, Y = 0, Width = 192, Height = 168 } }; diff --git a/profiles/sg0/hud/saveicon.lua b/profiles/sg0/hud/saveicon.lua new file mode 100644 index 000000000..789d4cfc5 --- /dev/null +++ b/profiles/sg0/hud/saveicon.lua @@ -0,0 +1,30 @@ +root.SaveIcon = { + ForegroundAnimation = "SaveIcon", + DefaultPosition = { X = 92, Y = 713 }, + BackgroundSprite = "SaveIconBg", + BackgroundOffset = { X = 0, Y = 0 }, + BackgroundMaxAlpha = 0.0, + FadeInDuration = 0.5, + FadeOutDuration = 0.25 +}; + +MakeAnimation({ + Name = "SaveIcon", + Sheet = "Data", + FirstFrameX = 115, + FirstFrameY = 456, + FrameWidth = 258, + ColWidth = 258, + FrameHeight = 178, + RowHeight = 178, + Frames = 2, + Duration = 0.4, + Rows = 2, + Columns = 1, + PrimaryDirection = AnimationDirections.Down +}); + +root.Sprites["SaveIconBg"] = { + Sheet = "Data", + Bounds = { X = 630, Y = 479, Width = 179, Height = 62 } +}; \ No newline at end of file diff --git a/profiles/sg0/hud/savemenu.lua b/profiles/sg0/hud/savemenu.lua new file mode 100644 index 000000000..7b9e51fb8 --- /dev/null +++ b/profiles/sg0/hud/savemenu.lua @@ -0,0 +1,69 @@ +-- STEINS;GATE 0 save / load / quick-load screen (savemenu.dds, sysm_savemenu.dds). +-- Rectangles were measured from the sheets' alpha; positions are 1920x1080 design pixels. +root.SaveMenu = { + Type = SaveMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + EmptyThumbnailSprite = "EmptyThumbnail", + FadeInDuration = 16 / 60, + FadeOutDuration = 16 / 60, + -- The scripts count SW_SYSSUBMENUCT up to 16 while the screen opens. + SubMenuCountMax = 16, + + BackgroundSprite = "SaveMenuBackground", + PanelSprite = "SaveMenuPanel", + PanelPos = { X = 230, Y = 34 }, + HeaderSprites = { "SaveMenuHeaderQuickLoad", "SaveMenuHeaderSave", "SaveMenuHeaderLoad" }, + HeaderPos = { X = -120, Y = -120 }, + ButtonGuideSprite = "SaveMenuButtonGuide", + ButtonGuidePos = { X = 1100, Y = 1012 }, + + NoDataSprite = "SaveMenuNoData", + EntryFrameSprite = "SaveMenuEntryFrame", + PlayTimeLabelSprite = "SaveMenuPlayTimeLabel", + SaveDateLabelSprite = "SaveMenuSaveDateLabel", + LockedSprite = "SaveMenuLocked", + PageDigitSprites = { + "SaveMenuPageSlash", "SaveMenuPage1", "SaveMenuPage2", "SaveMenuPage3", + "SaveMenuPage4", "SaveMenuPage5", "SaveMenuPage6" + }, + + -- Entry origin = PanelPos + EntryStart + (column, row) * EntrySpacing. + EntryStart = { X = 44, Y = 120 }, + EntrySpacing = { X = 800, Y = 216 }, + FrameOffset = { X = 0, Y = 0 }, + ThumbnailOffset = { X = 8, Y = 6 }, + ThumbnailSize = { X = 292, Y = 164 }, + PlayTimeLabelOffset = { X = 320, Y = 30 }, + SaveDateLabelOffset = { X = 320, Y = 90 }, + PlayTimeTextOffset = { X = 476, Y = 26 }, + SaveDateTextOffset = { X = 476, Y = 86 }, + LockedOffset = { X = 572, Y = 132 }, + PageNumberPos = { X = 1700, Y = 60 }, + TextFontSize = 32 +}; + +local function sprite(name, sheet, x, y, w, h) + root.Sprites[name] = { Sheet = sheet, Bounds = { X = x, Y = y, Width = w, Height = h } }; +end + +sprite("EmptyThumbnail", "SaveMenu", 0, 0, 0, 0); +sprite("SaveMenuBackground", "SysSaveMenu", 0, 0, 1920, 1080); +sprite("SaveMenuPanel", "SaveMenu", 4, 0, 1668, 1012); +sprite("SaveMenuHeaderSave", "SysSaveMenu", 8, 1088, 584, 564); +sprite("SaveMenuHeaderLoad", "SysSaveMenu", 608, 1088, 584, 564); +sprite("SaveMenuHeaderQuickLoad", "SysSaveMenu", 1208, 1088, 584, 564); +sprite("SaveMenuButtonGuide", "SysSaveMenu", 1100, 1700, 788, 60); + +sprite("SaveMenuNoData", "SaveMenu", 1684, 84, 292, 164); +sprite("SaveMenuEntryFrame", "SaveMenu", 1692, 336, 780, 176); +sprite("SaveMenuPlayTimeLabel", "SaveMenu", 1684, 4, 140, 32); +sprite("SaveMenuSaveDateLabel", "SaveMenu", 1684, 40, 140, 32); +sprite("SaveMenuLocked", "SaveMenu", 1984, 228, 196, 36); + +sprite("SaveMenuPageSlash", "SaveMenu", 2280, 84, 40, 36); +sprite("SaveMenuPage1", "SaveMenu", 2336, 88, 16, 32); +sprite("SaveMenuPage2", "SaveMenu", 2368, 84, 24, 36); +sprite("SaveMenuPage3", "SaveMenu", 2404, 84, 24, 36); +sprite("SaveMenuPage4", "SaveMenu", 2440, 88, 24, 32); +sprite("SaveMenuPage5", "SaveMenu", 2480, 84, 20, 36); +sprite("SaveMenuPage6", "SaveMenu", 2512, 84, 24, 36); diff --git a/profiles/sg0/hud/selectiondisplay.lua b/profiles/sg0/hud/selectiondisplay.lua new file mode 100644 index 000000000..33a038e05 --- /dev/null +++ b/profiles/sg0/hud/selectiondisplay.lua @@ -0,0 +1,75 @@ +root.SelectionDisplay = { + DrawType = DrawComponentType.Text, + SelectionBackgroundSprite = "SelectionBackground", + PlainSelectionFrameTopLeftSprite = "PlainSelectionFrameTopLeft", + PlainSelectionFrameTopSideSprite = "PlainSelectionFrameTopSide", + PlainSelectionFrameTopRightSprite = "PlainSelectionFrameTopRight", + PlainSelectionFrameLeftSideSprite = "PlainSelectionFrameLeftSide", + PlainSelectionFrameBottomLeftSprite = "PlainSelectionFrameBottomLeft", + PlainSelectionFrameRightSideSprite = "PlainSelectionFrameRightSide", + PlainSelectionFrameBottomRightSprite = "PlainSelectionFrameBottomRight", + PlainSelectionFrameBottomSideSprite = "PlainSelectionFrameBottomSide", + PlainSelectionFrameMiddleSprite = "PlainSelectionFrameMiddle", + SelectionHighlightSprite = "SelectionHighlight", + SelectionMaxCount = 5, + SelectionBackgroundX = 228, + SelectionBackgroundY = {277, 277, 196, 129, 95}, + SelectionYSpacing = 81, + PlainSelectionYSpacing = 10, + FadeAnimationDurationInOut = 0.2 +}; + +root.Sprites["SelectionBackground"] = { + Sheet = "Data", + Bounds = { X = 1, Y = 535, Width = 829, Height = 69 } +}; + +root.Sprites["PlainSelectionFrameTopLeft"] = { + Sheet = "Data", + Bounds = { X = 0, Y = 0, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameTopSide"] = { + Sheet = "Data", + Bounds = { X = 16, Y = 0, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameTopRight"] = { + Sheet = "Data", + Bounds = { X = 32, Y = 0, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameLeftSide"] = { + Sheet = "Data", + Bounds = { X = 0, Y = 16, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameBottomLeft"] = { + Sheet = "Data", + Bounds = { X = 0, Y = 32, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameRightSide"] = { + Sheet = "Data", + Bounds = { X = 32, Y = 16, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameBottomRight"] = { + Sheet = "Data", + Bounds = { X = 32, Y = 32, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameBottomSide"] = { + Sheet = "Data", + Bounds = { X = 16, Y = 32, Width = 16, Height = 16 } +}; + +root.Sprites["PlainSelectionFrameMiddle"] = { + Sheet = "Data", + Bounds = { X = 16, Y = 16, Width = 16, Height = 16 } +}; + +root.Sprites["SelectionHighlight"] = { + Sheet = "Data", + Bounds = { X = 65, Y = 33, Width = 94, Height = 30 } +}; diff --git a/profiles/sg0/hud/sysmesboxdisplay.lua b/profiles/sg0/hud/sysmesboxdisplay.lua new file mode 100644 index 000000000..4e1bc9631 --- /dev/null +++ b/profiles/sg0/hud/sysmesboxdisplay.lua @@ -0,0 +1,27 @@ +-- STEINS;GATE 0 system message (gsysmes.dds): full-screen backdrop, centred +-- message lines and the script's choice strings in one row below them. +root.SysMesBoxDisplay = { + Type = SysMesBoxType.SG0, + DrawType = DrawComponentType.SystemMessage, + BackgroundSprite = "SysMesBoxBackground", + BackgroundOpacity = 1.0, + MessageColor = 0xFFFFFF, + ChoiceColor = 0x9A9A9A, + ChoiceHighlightColor = 0xFFB040, + OutlineColor = 0x000000, + ChoiceSpacing = 160, + ChoicePadding = { X = 48, Y = 12 }, + TextFontSize = 40, + TextMiddleY = 470, + TextX = 960, + TextLineHeight = 56, + TextMarginY = 56, + AnimationSpeed = 25, + FadeInDuration = 0.25, + FadeOutDuration = 0.25 +}; + +root.Sprites["SysMesBoxBackground"] = { + Sheet = "SysMesBox", + Bounds = { X = 0, Y = 0, Width = 1920, Height = 1080 } +}; diff --git a/profiles/sg0/hud/systemmenu.lua b/profiles/sg0/hud/systemmenu.lua new file mode 100644 index 000000000..c35b379bc --- /dev/null +++ b/profiles/sg0/hud/systemmenu.lua @@ -0,0 +1,69 @@ +-- STEINS;GATE 0 system menu (menuchip.dds): the screen capture inside a monitor +-- frame and a ring of icon buttons. Entry i is the script's SW_SYSMENUCNO value: +-- 0 Backlog, 1 Quick Save, 2 Save, 3 Load, 4 Quick Load, 5 TIPS, 6 Config, +-- 7 Help, 8 Title, 9 Full Screen, 10 Exit Game, 11 Close Menu. +local sheet = "MenuChip"; + +local function sprite(name, x, y, w, h) + root.Sprites[name] = { Sheet = sheet, Bounds = { X = x, Y = y, Width = w, Height = h } }; + return name; +end + +root.SystemMenu = { + Type = SystemMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + -- _Startup_win counts SW_SYSMENUCT 0..96 in steps of 3. + CountMax = 96, + + BackgroundSprite = sprite("SystemMenuMonitor", 0, 0, 1920, 1080), + MonitorScreen = { X = 528, Y = 104, Width = 1164, Height = 884 }, + GlassSprite = sprite("SystemMenuGlass", 528, 1184, 1164, 884), + GlassOpacity = 0.3, + RingSprite = sprite("SystemMenuRing", 1924, 0, 532, 1072), + RingPos = { X = 1388, Y = 4 }, + + MenuEntriesNum = 12, + MenuEntriesHNum = 12, + MenuEntriesSprites = { + sprite("SystemMenuBacklog", 2640, 44, 136, 136), + sprite("SystemMenuQuickSave", 2524, 140, 100, 100), + sprite("SystemMenuSave", 2640, 200, 136, 136), + sprite("SystemMenuLoad", 2640, 352, 136, 136), + sprite("SystemMenuQuickLoad", 2524, 296, 100, 100), + sprite("SystemMenuTips", 2640, 508, 136, 136), + sprite("SystemMenuConfig", 2640, 660, 136, 136), + sprite("SystemMenuHelp", 2640, 812, 136, 136), + sprite("SystemMenuTitle", 2524, 756, 100, 100), + sprite("SystemMenuFullScreen", 2520, 596, 104, 104), + sprite("SystemMenuExit", 2520, 908, 104, 104), + sprite("SystemMenuClose", 2520, 452, 104, 104), + }, + MenuEntriesHighlightedSprites = { + sprite("SystemMenuBacklogGlow", 2904, 44, 136, 136), + sprite("SystemMenuQuickSaveGlow", 2796, 136, 104, 104), + sprite("SystemMenuSaveGlow", 2904, 200, 136, 136), + sprite("SystemMenuLoadGlow", 2904, 352, 136, 136), + sprite("SystemMenuQuickLoadGlow", 2796, 292, 104, 108), + sprite("SystemMenuTipsGlow", 2904, 504, 136, 140), + sprite("SystemMenuConfigGlow", 2904, 660, 136, 136), + sprite("SystemMenuHelpGlow", 2904, 812, 136, 136), + sprite("SystemMenuTitleGlow", 2796, 756, 104, 104), + sprite("SystemMenuFullScreenGlow", 2796, 600, 104, 104), + sprite("SystemMenuExitGlow", 2796, 908, 104, 104), + sprite("SystemMenuCloseGlow", 3036, 908, 104, 104), + }, + -- Button centres on screen: the ring's large circles sit on x 1616, the + -- small ones on x 1498 (Close at the ring's lower right). + EntryCenters = { + { X = 1616, Y = 106 }, { X = 1498, Y = 194 }, { X = 1616, Y = 269 }, + { X = 1616, Y = 422 }, { X = 1498, Y = 341 }, { X = 1616, Y = 575 }, + { X = 1616, Y = 728 }, { X = 1616, Y = 881 }, { X = 1498, Y = 810 }, + { X = 1498, Y = 657 }, { X = 1498, Y = 959 }, { X = 1734, Y = 959 }, + }, + -- Cursor order, top to bottom. + NavOrder = { 0, 1, 2, 4, 3, 5, 9, 6, 8, 7, 10, 11 }, + FocusScale = 1.12, + UnfocusedBrightness = 0.6, + FadeInDuration = 32 / 60, + FadeOutDuration = 32 / 60 +}; diff --git a/profiles/sg0/hud/tipsmenu.lua b/profiles/sg0/hud/tipsmenu.lua new file mode 100644 index 000000000..e8228bb29 --- /dev/null +++ b/profiles/sg0/hud/tipsmenu.lua @@ -0,0 +1,81 @@ +-- STEINS;GATE 0 TIPS (sysm_tips.dds backdrop, tipschips.dds panels). +-- Panel geometry measured from the tipschips overlay: list x 112-709 (tabs +-- y 111-157, rows y 160-1008), tip view x 778-1794 (header y 58-264, note +-- y 299-1008). The note text is laid out by the scripts' text mode 7. +root.SpriteSheets["SysTips"] = { Path = { Mount = "system", Id = 23 }, DesignWidth = 3072, DesignHeight = 1928 }; +root.SpriteSheets["TipsChips"] = { Path = { Mount = "system", Id = 24 }, DesignWidth = 3072, DesignHeight = 2168 }; + +local function sprite(name, sheet, x, y, w, h) + root.Sprites[name] = { Sheet = sheet, Bounds = { X = x, Y = y, Width = w, Height = h } }; + return name; +end + +root.TipsMenu = { + Type = TipsMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + -- _Startup_win counts SW_SYSSUBMENUCT to 32 while TIPS opens. + CountMax = 32, + FadeInDuration = 32 / 60, + FadeOutDuration = 32 / 60, + + BackgroundSprite = sprite("TipsBackground", "SysTips", 0, 0, 1920, 1080), + PanelsSprite = sprite("TipsPanels", "TipsChips", 0, 0, 1920, 1080), + HeaderSprite = sprite("TipsHeaderRing", "SysTips", 0, 1084, 600, 560), + HeaderPos = { X = -110, Y = 760 }, + HeaderIconSprite = sprite("TipsHeaderIcon", "SysTips", 2144, 216, 152, 144), + HeaderIconPos = { X = 114, Y = 968 }, + GuideSprite = sprite("TipsGuide", "SysTips", 856, 1700, 1032, 60), + GuidePos = { X = 864, Y = 1014 }, + + TabSprites = { + sprite("TipsTabAll", "TipsChips", 1968, 64, 68, 24), + sprite("TipsTabAcquired", "TipsChips", 2100, 64, 116, 32), + sprite("TipsTabUnread", "TipsChips", 2268, 64, 92, 24), + sprite("TipsTabNew", "TipsChips", 2428, 64, 80, 24), + }, + TabDisabledSprites = { + sprite("TipsTabAllOff", "TipsChips", 1968, 16, 68, 24), + sprite("TipsTabAcquiredOff", "TipsChips", 2100, 16, 116, 32), + sprite("TipsTabUnreadOff", "TipsChips", 2268, 16, 92, 24), + sprite("TipsTabNewOff", "TipsChips", 2428, 16, 80, 24), + }, + TabSelectedSprites = { + sprite("TipsTabAllOn", "TipsChips", 1924, 100, 152, 44), + sprite("TipsTabAcquiredOn", "TipsChips", 2080, 100, 152, 44), + sprite("TipsTabUnreadOn", "TipsChips", 2236, 100, 152, 44), + sprite("TipsTabNewOn", "TipsChips", 2392, 100, 152, 44), + }, + TabBounds = { + { X = 112, Y = 111, Width = 156, Height = 46 }, + { X = 268, Y = 111, Width = 156, Height = 46 }, + { X = 424, Y = 111, Width = 156, Height = 46 }, + { X = 580, Y = 111, Width = 129, Height = 46 }, + }, + + ListBounds = { X = 113, Y = 166, Width = 596, Height = 836 }, + ListTop = 174, + RowHeight = 48, + RowPadding = 6, + VisibleRows = 17, + ListNumberX = 128, + ListNameX = 214, + ListNameMaxWidth = 420, + ListFontSize = 32, + SelectionSprite = sprite("TipsSelection", "TipsChips", 1924, 148, 596, 44), + NewBadgeSprite = sprite("TipsNewBadge", "TipsChips", 2552, 80, 60, 32), + NewBadgeX = 642, + + NumberPos = { X = 870, Y = 76 }, + CategoryPos = { X = 1440, Y = 76 }, + NamePos = { X = 806, Y = 166 }, + NameMaxWidth = 970, + HeaderFontSize = 36, + NameFontSize = 48, + NoteBounds = { X = 790, Y = 350, Width = 1000, Height = 650 }, + NoteThumbSprite = sprite("TipsNoteThumb", "TipsChips", 2852, 16, 12, 60), + NoteThumbX = 1799, + NoteScrollSpeed = 600, + + TextColor = 0xFFFFFF, + LockedColor = 0x8A8A8A +}; diff --git a/profiles/sg0/hud/tipsnotification.lua b/profiles/sg0/hud/tipsnotification.lua new file mode 100644 index 000000000..4d80584c8 --- /dev/null +++ b/profiles/sg0/hud/tipsnotification.lua @@ -0,0 +1,3 @@ +root.TipsNotification = { + Type = TipsNotificationType.None, +}; diff --git a/profiles/sg0/hud/titlemenu.lua b/profiles/sg0/hud/titlemenu.lua new file mode 100644 index 000000000..db08db4cb --- /dev/null +++ b/profiles/sg0/hud/titlemenu.lua @@ -0,0 +1,63 @@ +-- STEINS;GATE 0 (Steam) title menu. Sprite rectangles come from system.mpk +-- title_chip.dds and title_logo3.dds (scripts/sheet_regions.py); positions are a +-- first functional layout over the intro movie the title script plays. +root.TitleMenu = { + Type = TitleMenuType.SG0, + DrawType = DrawComponentType.SystemMenu, + PressToStartPos = { X = 728, Y = 880 }, + PressToStartAnimDurationIn = 0.7, + PressToStartAnimDurationOut = 0.7, + PressToStartAnimFastDurationIn = 0.1, + PressToStartAnimFastDurationOut = 0.1, + PressToStartSprite = "TitleMenuPressToStart", + LogoSprite = "TitleMenuLogo", + LogoPos = { X = 432, Y = 150 }, + ItemPos = { X = 120, Y = 640 }, + ItemSpacing = 52, + SubItemOffsetX = 300, + SubItemSpacing = 48, + MenuEntriesNum = 5, + MenuEntriesSprites = {}, + MenuEntriesHighlightedSprites = {}, + SubItemSprites = {}, + SubItemHighlightedSprites = {}, +}; + +root.Sprites["TitleMenuPressToStart"] = { + Sheet = "TitleChip", + Bounds = { X = 8, Y = 1428, Width = 464, Height = 40 }, +}; +root.Sprites["TitleMenuLogo"] = { + Sheet = "TitleLogo", + Bounds = { X = 48, Y = 1788, Width = 1056, Height = 284 }, +}; + +-- NEW GAME, CONTINUE, EXTRA, CONFIG, HELP: 42 px apart from y 1088. +-- Normal items at x 648; highlighted items (with their left bar) at x 920. +for i = 0, 4 do + root.Sprites["TitleMenuEntry" .. i] = { + Sheet = "TitleChip", + Bounds = { X = 648, Y = 1088 + i * 42, Width = 268, Height = 40 }, + }; + root.Sprites["TitleMenuEntryHighlight" .. i] = { + Sheet = "TitleChip", + Bounds = { X = 920, Y = 1088 + i * 42, Width = 268, Height = 40 }, + }; + root.TitleMenu.MenuEntriesSprites[i + 1] = "TitleMenuEntry" .. i; + root.TitleMenu.MenuEntriesHighlightedSprites[i + 1] = "TitleMenuEntryHighlight" .. i; +end + +-- LOAD, QUICK LOAD, CLEAR LIST, CG LIBRARY, SOUND LIBRARY, MOVIE LIBRARY, TIPS: +-- 48 px apart from y 1088; normal at x 4, highlighted at x 328. +for i = 0, 6 do + root.Sprites["TitleMenuSubEntry" .. i] = { + Sheet = "TitleChip", + Bounds = { X = 4, Y = 1088 + i * 48, Width = 312, Height = 40 }, + }; + root.Sprites["TitleMenuSubEntryHighlight" .. i] = { + Sheet = "TitleChip", + Bounds = { X = 328, Y = 1088 + i * 48, Width = 312, Height = 40 }, + }; + root.TitleMenu.SubItemSprites[i + 1] = "TitleMenuSubEntry" .. i; + root.TitleMenu.SubItemHighlightedSprites[i + 1] = "TitleMenuSubEntryHighlight" .. i; +end diff --git a/profiles/sg0/movie.cls b/profiles/sg0/movie.cls new file mode 100644 index 000000000..9501a7934 --- /dev/null +++ b/profiles/sg0/movie.cls @@ -0,0 +1,33 @@ +END01 +END02 +END03 +END04 +IMV001 +IMV002 +IMV003 +IMV004 +IMV005 +IMV006 +IMV007 +IMV008 +IMV009 +IMV010 +IMV011 +IMV012 +OP +TIT_000 +TIT_001 +TIT_A01 +TIT_A02 +TIT_A03 +TIT_A04 +TIT_B01 +TIT_B02 +TIT_B03A +TIT_B03B +TIT_B04 +TIT_K01 +TIT_M01 +TIT_T01 +anisama +intro diff --git a/profiles/sg0/savedata.lua b/profiles/sg0/savedata.lua new file mode 100644 index 000000000..7db5bd3d6 --- /dev/null +++ b/profiles/sg0/savedata.lua @@ -0,0 +1,266 @@ +-- STEINS;GATE 0 (Steam) save data. Uses Impacto's CCLCC (PS4 layout) checkpoint save +-- implementation with SG0's own data; files live under the run's saves folder, never +-- the original Steam saves. +root.SaveData = { + Type = SaveDataType.CCLCC_PS4, + SaveFilePath = root.BasePaths.RootSavesDir .. "/sg0/SAVEDATA.DAT", + -- Per script id (script.mpk entry order): line count (= string table size, the upper + -- bound of message line ids) and offset into the read-text bit array (30567 lines total). + ScriptMessageData = { + {0x3d2, 0x0}, {0x0, 0x3d2}, {0x6e, 0x3d2}, {0x6e, 0x440}, {0x5d, 0x4ae}, {0x78, 0x50b}, + {0x67, 0x583}, {0xdc, 0x5ea}, {0x40c, 0x6c6}, {0x0, 0xad2}, {0x23, 0xad2}, {0x0, 0xaf5}, + {0x24, 0xaf5}, {0x1, 0xb19}, {0x1, 0xb1a}, {0x0, 0xb1b}, {0x60, 0xb1b}, {0x1ec, 0xb7b}, + {0xc1, 0xd67}, {0x9a, 0xe28}, {0x26e, 0xec2}, {0xcb, 0x1130}, {0x129, 0x11fb}, {0x16e, 0x1324}, + {0x1a0, 0x1492}, {0xd7, 0x1632}, {0x4e, 0x1709}, {0xff, 0x1757}, {0x1a, 0x1856}, {0xf2, 0x1870}, + {0x14f, 0x1962}, {0xbe, 0x1ab1}, {0x1a, 0x1b6f}, {0x12c, 0x1b89}, {0x65, 0x1cb5}, {0xdb, 0x1d1a}, + {0x100, 0x1df5}, {0xb0, 0x1ef5}, {0xe7, 0x1fa5}, {0x14d, 0x208c}, {0x118, 0x21d9}, {0x6d, 0x22f1}, + {0x56, 0x235e}, {0xdb, 0x23b4}, {0x111, 0x248f}, {0x1c6, 0x25a0}, {0xfd, 0x2766}, {0x86, 0x2863}, + {0x18b, 0x28e9}, {0x56, 0x2a74}, {0x7d, 0x2aca}, {0x18a, 0x2b47}, {0x100, 0x2cd1}, {0x50, 0x2dd1}, + {0x19f, 0x2e21}, {0x2b, 0x2fc0}, {0x139, 0x2feb}, {0x49, 0x3124}, {0x111, 0x316d}, {0x1a9, 0x327e}, + {0x183, 0x3427}, {0xe9, 0x35aa}, {0x189, 0x3693}, {0x1db, 0x381c}, {0x72, 0x39f7}, {0x7f, 0x3a69}, + {0x1a3, 0x3ae8}, {0x187, 0x3c8b}, {0xef, 0x3e12}, {0xd8, 0x3f01}, {0x66, 0x3fd9}, {0x216, 0x403f}, + {0x245, 0x4255}, {0x1c5, 0x449a}, {0x5a, 0x465f}, {0x18c, 0x46b9}, {0x274, 0x4845}, {0x108, 0x4ab9}, + {0x57, 0x4bc1}, {0x95, 0x4c18}, {0x107, 0x4cad}, {0xda, 0x4db4}, {0x2a3, 0x4e8e}, {0xa2, 0x5131}, + {0xf2, 0x51d3}, {0x5e, 0x52c5}, {0x1b7, 0x5323}, {0xa5, 0x54da}, {0x81, 0x557f}, {0x110, 0x5600}, + {0x363, 0x5710}, {0x1a, 0x5a73}, {0xa2, 0x5a8d}, {0x119, 0x5b2f}, {0x1af, 0x5c48}, {0x202, 0x5df7}, + {0x80, 0x5ff9}, {0xe4, 0x6079}, {0xee, 0x615d}, {0xd3, 0x624b}, {0x199, 0x631e}, {0x79, 0x64b7}, + {0xcf, 0x6530}, {0x24, 0x65ff}, {0x215, 0x6623}, {0x64, 0x6838}, {0x66, 0x689c}, {0x50, 0x6902}, + {0x3d, 0x6952}, {0xba, 0x698f}, {0x2d, 0x6a49}, {0x10e, 0x6a76}, {0x1f7, 0x6b84}, {0x1f4, 0x6d7b}, + {0x19c, 0x6f6f}, {0x7d, 0x710b}, {0x1d5, 0x7188}, {0x17e, 0x735d}, {0x56, 0x74db}, {0xbc, 0x7531}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, {0x0, 0x75ed}, + {0x0, 0x75ed}, {0x0, 0x75ed}, {0x17a, 0x75ed}, {0x0, 0x7767}, + }, + -- CG Library (album_thum.dds, 10 per row): per thumbnail the EV flags (SetEVflag ids) + -- of its variations, and the bg.mpk ids they show. SG0_EV variations (minus the + -- text-free "x0" alternates) take flags 20-187 in bg order; EV_C01..Z03 take 1-19. + -- Flag 0 is never set: the EV_SP pictures have no EV flag (the library unlocks them + -- when all six endings are cleared) and thumbnail 101 has no known CG. + AlbumEvData = { + {20, 21}, -- 0 SG0_EV001 + {22, 23, 24, 25, 26, 27, 28}, -- 1 SG0_EV002 + {29, 30}, -- 2 SG0_EV003 + {31, 32, 33}, -- 3 SG0_EV004 + {34, 35}, -- 4 SG0_EV005 + {36}, -- 5 SG0_EV006 + {37}, -- 6 SG0_EV007 + {38, 39, 40}, -- 7 SG0_EV008 + {41, 42}, -- 8 SG0_EV009 + {43, 44, 45}, -- 9 SG0_EV010 + {46, 47}, -- 10 SG0_EV011 + {48, 49}, -- 11 SG0_EV012 + {50}, -- 12 SG0_EV013 + {51, 52}, -- 13 SG0_EV014 + {53}, -- 14 SG0_EV015 + {54}, -- 15 SG0_EV017 + {55}, -- 16 SG0_EV018 + {56}, -- 17 SG0_EV019 + {57}, -- 18 SG0_EV020 + {58, 59}, -- 19 SG0_EV021 + {60}, -- 20 SG0_EV022 + {61, 62}, -- 21 SG0_EV023 + {63, 64}, -- 22 SG0_EV024 + {65}, -- 23 SG0_EV025 + {66}, -- 24 SG0_EV026 + {67, 68, 69}, -- 25 SG0_EV027 + {70, 71, 72}, -- 26 SG0_EV028 + {73, 74, 75}, -- 27 SG0_EV029 + {76, 77}, -- 28 SG0_EV030 + {78, 79, 80}, -- 29 SG0_EV031 + {81}, -- 30 SG0_EV032 + {82}, -- 31 SG0_EV033 + {83, 84, 85}, -- 32 SG0_EV034 + {86}, -- 33 SG0_EV035 + {87}, -- 34 SG0_EV036 + {88, 89, 90}, -- 35 SG0_EV038 + {91, 92}, -- 36 SG0_EV039 + {93, 94, 95, 96}, -- 37 SG0_EV040 + {97, 98}, -- 38 SG0_EV041 + {99, 100, 101}, -- 39 SG0_EV042 + {102, 103}, -- 40 SG0_EV043 + {104, 105}, -- 41 SG0_EV044 + {106}, -- 42 SG0_EV045 + {107, 108, 109, 110}, -- 43 SG0_EV046 + {111, 112, 113, 114, 115, 116}, -- 44 SG0_EV047 + {117, 118}, -- 45 SG0_EV048 + {119, 120, 121, 122}, -- 46 SG0_EV049 + {123, 124, 125}, -- 47 SG0_EV050 + {126, 127, 128, 129, 130}, -- 48 SG0_EV051 + {131, 132}, -- 49 SG0_EV052 + {133, 134}, -- 50 SG0_EV053 + {135}, -- 51 SG0_EV054 + {136, 137, 138}, -- 52 SG0_EV055 + {139, 140, 141}, -- 53 SG0_EV056 + {142}, -- 54 SG0_EV057 + {143, 144}, -- 55 SG0_EV058a-b + {145}, -- 56 SG0_EV058c + {146}, -- 57 SG0_EV059 + {147, 148, 149}, -- 58 SG0_EV060 + {150, 151, 152}, -- 59 SG0_EV061 + {153}, -- 60 SG0_EV062 + {154, 155}, -- 61 SG0_EV063 + {156}, -- 62 SG0_EV064 + {157}, -- 63 SG0_EV065 + {158, 159}, -- 64 SG0_EV066 + {160}, -- 65 SG0_EV067 + {161, 162}, -- 66 SG0_EV068 + {163}, -- 67 SG0_EV069 + {164, 165, 166}, -- 68 SG0_EV070 + {167}, -- 69 SG0_EV071 + {168, 169}, -- 70 SG0_EV072 + {170, 171}, -- 71 SG0_EV073 + {172}, -- 72 SG0_EV074 + {173}, -- 73 SG0_EV075 + {174}, -- 74 SG0_EV076 + {175}, -- 75 SG0_EV077 + {176}, -- 76 SG0_EV078 + {177, 178}, -- 77 SG0_EV079 + {179}, -- 78 SG0_EV080 + {180}, -- 79 SG0_EV081 + {181, 182}, -- 80 SG0_EV082 + {183, 184, 185}, -- 81 SG0_EV083 + {1}, -- 82 EV_C01A + {2}, -- 83 EV_C02A + {3}, -- 84 EV_C04A + {4}, -- 85 EV_C05A + {5}, -- 86 EV_C07A + {6}, -- 87 EV_C08A + {7}, -- 88 EV_C09B + {8}, -- 89 EV_C11A + {9}, -- 90 EV_K03C + {10}, -- 91 EV_M01A + {11}, -- 92 EV_M05A + {12}, -- 93 EV_M07A + {13}, -- 94 EV_M12A5 + {14}, -- 95 EV_M13A + {15}, -- 96 EV_S09A + {16}, -- 97 EV_X01B + {17}, -- 98 EV_X09A + {18}, -- 99 EV_Z02B + {19}, -- 100 EV_Z03A + {0}, -- 101 unidentified (no CG source found) + {0}, -- 102 EV_SP01 (no EV flag) + {0}, -- 103 EV_SP02 (no EV flag) + {0}, -- 104 EV_SP03 (no EV flag) + {0}, -- 105 EV_SP04 (no EV flag) + {186}, -- 106 SG0_EV084 + {187}, -- 107 SG0_EV085 + }, + AlbumData = { + {{402}, {403}}, -- 0 + {{404}, {405}, {406}, {407}, {408}, {409}, {410}}, -- 1 + {{411}, {412}}, -- 2 + {{414}, {415}, {416}}, -- 3 + {{417}, {418}}, -- 4 + {{419}}, -- 5 + {{420}}, -- 6 + {{421}, {422}, {423}}, -- 7 + {{424}, {425}}, -- 8 + {{426}, {427}, {428}}, -- 9 + {{429}, {430}}, -- 10 + {{431}, {432}}, -- 11 + {{433}}, -- 12 + {{434}, {435}}, -- 13 + {{436}}, -- 14 + {{437}}, -- 15 + {{438}}, -- 16 + {{439}}, -- 17 + {{440}}, -- 18 + {{441}, {442}}, -- 19 + {{443}}, -- 20 + {{444}, {445}}, -- 21 + {{446}, {447}}, -- 22 + {{448}}, -- 23 + {{449}}, -- 24 + {{450}, {451}, {452}}, -- 25 + {{453}, {454}, {455}}, -- 26 + {{456}, {457}, {458}}, -- 27 + {{459}, {460}}, -- 28 + {{461}, {462}, {463}}, -- 29 + {{464}}, -- 30 + {{465}}, -- 31 + {{466}, {467}, {468}}, -- 32 + {{469}}, -- 33 + {{470}}, -- 34 + {{471}, {472}, {473}}, -- 35 + {{474}, {475}}, -- 36 + {{476}, {477}, {478}, {479}}, -- 37 + {{480}, {481}}, -- 38 + {{482}, {483}, {484}}, -- 39 + {{485}, {486}}, -- 40 + {{487}, {488}}, -- 41 + {{489}}, -- 42 + {{490}, {491}, {492}, {493}}, -- 43 + {{494}, {495}, {496}, {497}, {498}, {499}}, -- 44 + {{500}, {501}}, -- 45 + {{502}, {503}, {504}, {505}}, -- 46 + {{506}, {507}, {508}}, -- 47 + {{509}, {510}, {512}, {514}, {516}}, -- 48 + {{517}, {518}}, -- 49 + {{519}, {520}}, -- 50 + {{521}}, -- 51 + {{522}, {523}, {524}}, -- 52 + {{525}, {526}, {527}}, -- 53 + {{528}}, -- 54 + {{529}, {530}}, -- 55 + {{531}}, -- 56 + {{532}}, -- 57 + {{533}, {534}, {535}}, -- 58 + {{536}, {537}, {539}}, -- 59 + {{541}}, -- 60 + {{542}, {543}}, -- 61 + {{544}}, -- 62 + {{545}}, -- 63 + {{546}, {547}}, -- 64 + {{548}}, -- 65 + {{549}, {550}}, -- 66 + {{551}}, -- 67 + {{552}, {553}, {554}}, -- 68 + {{555}}, -- 69 + {{556}, {557}}, -- 70 + {{558}, {559}}, -- 71 + {{560}}, -- 72 + {{561}}, -- 73 + {{562}}, -- 74 + {{563}}, -- 75 + {{564}}, -- 76 + {{565}, {566}}, -- 77 + {{567}}, -- 78 + {{568}}, -- 79 + {{569}, {570}}, -- 80 + {{572}, {573}, {574}}, -- 81 + {{369}}, -- 82 + {{370}}, -- 83 + {{371}}, -- 84 + {{372}}, -- 85 + {{373}}, -- 86 + {{374}}, -- 87 + {{375}}, -- 88 + {{376}}, -- 89 + {{377}}, -- 90 + {{378}}, -- 91 + {{379}}, -- 92 + {{380}}, -- 93 + {{381}}, -- 94 + {{382}}, -- 95 + {{383}}, -- 96 + {{388}}, -- 97 + {{389}}, -- 98 + {{390}}, -- 99 + {{391}}, -- 100 + {{391}}, -- 101 + {{384}}, -- 102 + {{385}}, -- 103 + {{386}}, -- 104 + {{387}}, -- 105 + {{575}}, -- 106 + {{576}}, -- 107 + }, +}; diff --git a/profiles/sg0/scriptvars.lua b/profiles/sg0/scriptvars.lua new file mode 100644 index 000000000..8344722ef --- /dev/null +++ b/profiles/sg0/scriptvars.lua @@ -0,0 +1,30 @@ +local sv = root.ScriptVars; + +sv.SW_TITLECUR1 = 2139; +sv.SW_TITLECUR2 = 2140; --Actual guess +sv.SW_BG1POSX_OFS = 2400; +sv.SW_BG1POSY_OFS = 2401; +sv.SW_BG1SX_OFS = 2402; +sv.SW_BG1SY_OFS = 2403; +sv.SW_BG1SIZE_OFS = 2404; +sv.SW_BG1LX_OFS = 2405; +sv.SW_BG1LY_OFS = 2406; +sv.SW_BG1ROTZ_OFS = 2407; +sv.SW_BG1ALPHA_OFS = 2408; +sv.SW_MASK1COLOR = 4400; +sv.SW_MASK1ALPHA = 4401; +sv.SW_MASK1PRI = 4402; +sv.SW_MASK1POSX = 4403; +sv.SW_MASK1POSY = 4404; +sv.SW_MASK1SIZEX = 4405; +sv.SW_MASK1SIZEY = 4406; +sv.SW_MESMODE0 = 4363; +-- Cut-in slots (macrosys label658: 5800, 5820, 5840, 5860). +sv.SW_CUTIN1 = 5800; + +sv.SF_SYSTEMMENUCAPTURE=1378; +-- SG0 reuses CC's SW_MESWIN0TYPE slot (4422) for an effect state (values 99/33/34/14), +-- which made the dialogue pages read "don't draw text". SG0's own message-window type +-- variable is not identified; 4430/4440/4450 (pages 0-2, stride 10) are never written by +-- any SG0 script, so the window type reads 0 (normal window and text). +sv.SW_MESWIN0TYPE = 4430; diff --git a/profiles/sg0/sprites.lua b/profiles/sg0/sprites.lua new file mode 100644 index 000000000..22b2f9da6 --- /dev/null +++ b/profiles/sg0/sprites.lua @@ -0,0 +1,81 @@ +root.SpriteSheets = { + ["BacklogMask"] = { + Path = { Mount = "system", Id = 1 }, + DesignWidth = 2048, + DesignHeight = 2208 + }, + ["Data"] = { + Path = { Mount = "system", Id = 4 }, + DesignWidth = 3200, + DesignHeight = 1536 + }, + ["MesBox"] = { + Path = { Mount = "system", Id = 16 }, + DesignWidth = 3072, + DesignHeight = 1728 + }, + ["TitleLogo"] = { + -- title_logo3.dds: last ring frames and the Steins;Gate wordmark + Path = { Mount = "system", Id = 28 }, + DesignWidth = 3840, + DesignHeight = 2304 + }, + ["Font"] = { + -- One 64x125 grid of 48x48 px glyph cells split across two pages: + -- font_a.dds rows 0-84 (4080 px, then 16 px of padding), font_b.dds rows 85-124. + Path = { Mount = "system", Id = 9 }, + ContinuationPath = { Mount = "system", Id = 10 }, + FirstPageHeight = 4080, + DesignWidth = 3072, + DesignHeight = 6000 + }, + ["Menu"] = { + Path = { Mount = "system", Id = 3 }, + DesignWidth = 3072, + DesignHeight = 2496 + }, + ["Title"] = { + Path = { Mount = "system", Id = 26 }, + DesignWidth = 3840, + DesignHeight = 3840 + }, + ["TitleChip"] = { + Path = { Mount = "system", Id = 25 }, + DesignWidth = 3072, + DesignHeight = 1536 + }, + ["MenuChip"] = { + Path = { Mount = "system", Id = 15 }, + DesignWidth = 3264, + DesignHeight = 2176 + }, + ["SysMesBox"] = { + Path = { Mount = "system", Id = 13 }, + DesignWidth = 2048, + DesignHeight = 1440 + }, + ["Backlog"] = { + Path = { Mount = "system", Id = 1 }, + DesignWidth = 2048, + DesignHeight = 2208 + }, + ["NamePlate"] = { + Path = { Mount = "system", Id = 16 }, + DesignWidth = 3072, + DesignHeight = 1728 + }, + ["SaveMenu"] = { + -- savemenu.dds: slot panel, selection frame, slot labels, page digits + Path = { Mount = "system", Id = 21 }, + DesignWidth = 3072, + DesignHeight = 2048 + }, + ["SysSaveMenu"] = { + -- sysm_savemenu.dds: background, SAVE / LOAD / QUICK LOAD emblems, button guides + Path = { Mount = "system", Id = 22 }, + DesignWidth = 3072, + DesignHeight = 1848 + } +}; + +root.Sprites = {}; \ No newline at end of file diff --git a/profiles/sg0/tipssystem.lua b/profiles/sg0/tipssystem.lua new file mode 100644 index 000000000..50fa374a5 --- /dev/null +++ b/profiles/sg0/tipssystem.lua @@ -0,0 +1,8 @@ +-- SG0's _tips.scx uses the CHAOS;CHILD Love Chu Chu tips table: per tip a u16 +-- note-string count, then category, name, sort key, list name and the notes. +root.TipsSystem = { + Type = TipsSystemType.CCLCC, + MaxTipsCount = 300, + TipsTextTableId = 2, + TipsTextSortStrIndex = 10, +}; diff --git a/profiles/sg0/vfs.lua b/profiles/sg0/vfs.lua new file mode 100644 index 000000000..bb8cb7a08 --- /dev/null +++ b/profiles/sg0/vfs.lua @@ -0,0 +1,23 @@ +-- STEINS;GATE 0 Steam (Windows) layout: USRDIR/*.mpk plus Bink 2 movies in USRDIR/movie//. +root.Vfs = { + Mounts = { + ["script"] = {root.BasePaths.RootGamedataDir .. "/sg0/script.mpk"}, + ["system"] = {root.BasePaths.RootGamedataDir .. "/sg0/system.mpk"}, + ["bgm"] = {root.BasePaths.RootGamedataDir .. "/sg0/bgm.mpk"}, + ["se"] = {root.BasePaths.RootGamedataDir .. "/sg0/se.mpk"}, + ["voice"] = {root.BasePaths.RootGamedataDir .. "/sg0/voice.mpk"}, + ["bg"] = {root.BasePaths.RootGamedataDir .. "/sg0/bg.mpk"}, + ["chara"] = {root.BasePaths.RootGamedataDir .. "/sg0/chara.mpk"}, + ["mask"] = {root.BasePaths.RootGamedataDir .. "/sg0/mask.mpk"}, + ["manual"] = {root.BasePaths.RootGamedataDir .. "/sg0/manual.mpk"}, + ["movie"] = { + { + Path = root.BasePaths.RootGamedataDir .. "/sg0/movie/1920x1080", + Whitelist = ".*\\.bk2", + -- Movie ids follow plain ASCII order of the file names (uppercase first): + -- the scripts play 32 = intro and 16 = OP at the title, 4/8-10 = IMV inserts. + Order = root.BasePaths.RootProfilesDir .. "/sg0/movie.cls", + }, + } + } +}; diff --git a/profiles/sg0/waveeffects.lua b/profiles/sg0/waveeffects.lua new file mode 100644 index 000000000..9bfa70ee0 --- /dev/null +++ b/profiles/sg0/waveeffects.lua @@ -0,0 +1,4 @@ +root.WaveEffects = { + WaveMaxCount = 20; + BGWaveGridSize = { X = 160, Y = 90 } +}; diff --git a/src/background2d.cpp b/src/background2d.cpp index f48d8dfc5..56be0ff20 100644 --- a/src/background2d.cpp +++ b/src/background2d.cpp @@ -466,6 +466,7 @@ void Background2D::UpdateState(const int bgId) { switch (GameInstructionSet) { case Vm::InstructionSet::CC: + case Vm::InstructionSet::SG0: case Vm::InstructionSet::LCCSwitch: Tint = ScrWorkGetColor(SW_BG1FILTER + structOffset); break; @@ -633,6 +634,7 @@ void Capture2D::UpdateState(const int capId) { switch (GameInstructionSet) { case Vm::InstructionSet::CC: + case Vm::InstructionSet::SG0: case Vm::InstructionSet::LCCSwitch: Tint = ScrWorkGetColor(SW_CAP1FILTER + structOffset); break; @@ -733,6 +735,7 @@ void BackgroundEffect2D::UpdateState(const int bgId) { // Set tint switch (GameInstructionSet) { case Vm::InstructionSet::CC: + case Vm::InstructionSet::SG0: case Vm::InstructionSet::LCCSwitch: Tint = ScrWorkGetColor(SW_BGEFF1_FILTER + structOffset); break; diff --git a/src/data/savesystem.cpp b/src/data/savesystem.cpp index 7aedf9caf..d8d53a08f 100644 --- a/src/data/savesystem.cpp +++ b/src/data/savesystem.cpp @@ -55,13 +55,19 @@ class SaveFileChecker : public Loadable { protected: void UnloadSync() {} SaveError LoadSync() { - return Implementation ? Implementation->CheckSaveFile() : SaveError::Failed; + if (Implementation) return Implementation->CheckSaveFile(); + // Without a save implementation STEINS;GATE 0 boots as a fresh install: + // its startup script then creates new system data instead of loading it. + return Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 + ? SaveError::NotFound + : SaveError::Failed; } void MainThreadOnLoad(SaveError result) { // Let's not report errors until we finalize the implementation if (Profile::Vm::GameInstructionSet != Vm::InstructionSet::CC && - Profile::Vm::GameInstructionSet != Vm::InstructionSet::CHLCC) { + Profile::Vm::GameInstructionSet != Vm::InstructionSet::CHLCC && + Profile::Vm::GameInstructionSet != Vm::InstructionSet::SG0) { result = SaveError::OK; } diff --git a/src/game.cpp b/src/game.cpp index 27603c199..be56683a5 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -36,6 +36,9 @@ #include "hud/tipsnotification.h" #include "hud/achievementnotification.h" #include "games/cclcc/systemmenu.h" +#include "games/sg0/phone.h" +#include "games/sg0/systemmenu.h" +#include "games/sg0/cutin.h" #include "effects/wave.h" #include "effects/blur.h" #include "effects/mosaic.h" @@ -344,6 +347,7 @@ void UpdateSystem(float dt) { Vm::Interface::UpdatePADInput(); Vm::Interface::UpdatePADHoldInput(updateInterval); Vm::Interface::UpdateKBHoldInput(updateInterval); + if (UI::SG0::PhonePtr) UI::SG0::PhonePtr->TakeInput(updateInterval); UpdateGameState(updateInterval); for (DrawComponentType value : @@ -372,6 +376,7 @@ void UpdateSystem(float dt) { } const bool isCC = Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 || Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch; if (GetFlag(SF_BGEFF1DISP) && (!isCC || ScrWork[SW_EFF_WAVE_ALPHA])) { Effects::WaveEFF.Update(updateInterval); @@ -443,6 +448,7 @@ static void RenderMain() { for (uint32_t layer = 0; layer <= Profile::Game::LayerCount; layer++) { if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 || Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { const int renderTarget = ScrWork[SW_RENDERTARGET + layer]; if (0 <= renderTarget && renderTarget <= MaxFramebuffers) { @@ -475,6 +481,10 @@ static void RenderMain() { Background2D::LastRenderedBackground->RenderBgEff(layer); } + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0) { + SG0::RenderCutins(static_cast(layer)); + } + if ((+Profile::Game::GameFeatures & +GameFeature::Renderer2D) && !(+Profile::Game::GameFeatures & +GameFeature::Scene3D)) { for (int chaId = 0; chaId < std::ssize(Characters2D); chaId++) { @@ -555,6 +565,7 @@ static void RenderMain() { if (Profile::Game::UseWaveEffects) { const bool isCC = Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 || Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch; if (GetFlag(SF_BGEFF1DISP) && (!isCC || ScrWork[SW_EFF_WAVE_ALPHA])) { if (ScrWork[SW_EFF_WAVE_PRI] == static_cast(layer)) { @@ -666,6 +677,13 @@ void Render() { static_cast(UI::SystemMenuPtr) ->ScreenCap); SetFlag(SF_SYSTEMMENUCAPTURE, false); + } else if (Profile::Vm::GameInstructionSet == + Vm::InstructionSet::SG0 && + GetFlag(SF_SYSTEMMENUCAPTURE) && UI::SystemMenuPtr) { + Renderer->CaptureScreencap( + static_cast(UI::SystemMenuPtr) + ->ScreenCap); + SetFlag(SF_SYSTEMMENUCAPTURE, false); } KeyWaitIcon->Render({0.0f, 0.0f}, glm::vec4(1.0f)); @@ -677,6 +695,7 @@ void Render() { } case DrawComponentType::Main: { if ((Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 || Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) && !(!GetFlag(SF_SELECTMODE) || @@ -702,6 +721,7 @@ void Render() { } case DrawComponentType::SystemIcons: { if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::CC || + Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 || Profile::Vm::GameInstructionSet == Vm::InstructionSet::LCCSwitch) { UI::GameSpecific::RenderCCButtonGuide(); @@ -744,6 +764,15 @@ void Render() { menu->Render(); } } + // SG0 plays movies outside the story (EXTRA > MOVIE LIBRARY) on movie + // priorities above every scene layer (3000, 4000): draw those over the + // whole frame. + if (Profile::Vm::GameInstructionSet == Vm::InstructionSet::SG0 && + +Profile::Game::GameFeatures & +GameFeature::Video && + ScrWork[SW_MOVIEPRI] > static_cast(Profile::Game::LayerCount) && + ScrWork[SW_MOVIEALPHA] > 0) { + Video::VideoRender(ScrWork[SW_MOVIEALPHA] / 256.0f); + } if (+Profile::Game::GameFeatures & +GameFeature::Achievements) { AchievementNotification::Render(); } diff --git a/src/games/sg0/backlogmenu.cpp b/src/games/sg0/backlogmenu.cpp new file mode 100644 index 000000000..b2bafc4fa --- /dev/null +++ b/src/games/sg0/backlogmenu.cpp @@ -0,0 +1,92 @@ +#include "backlogmenu.h" + +#include + +#include "../../profile/games/sg0/backlogmenu.h" +#include "../../profile/ui/backlogmenu.h" +#include "../../profile/game.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::BacklogMenu; +using namespace Impacto::Profile::SG0::BacklogMenu; +using namespace Impacto::Profile::ScriptVars; + +namespace { +constexpr int SubMenuNo = 1; +constexpr int AlphaMax = 256; +} // namespace + +void BacklogMenu::Hide() { + Audio::Channels[Audio::AC_REV]->Stop(0.0f); + UI::BacklogMenu::Hide(); +} + +void BacklogMenu::UpdateVisibility() { + const int alpha = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo ? ScrWork[SW_SYSSUBMENUALPHA] : 0; + + if (alpha > LastAlpha && State != Shown && State != Showing) { + Show(); + } else if (alpha < LastAlpha && State != Hidden && State != Hiding) { + Hide(); + } + LastAlpha = alpha; + + // The script owns the fade; mirror it so the base class's highlight and + // entries use the same opacity. + FadeAnimation.Progress = std::clamp(alpha / float(AlphaMax), 0.0f, 1.0f); + + if (State == Showing && alpha >= AlphaMax) { + State = Shown; + IsFocused = UI::FocusedMenu == this; + } else if (State == Hiding && alpha <= 0) { + State = Hidden; + IsFocused = false; + for (auto& entry : Entries) entry.Hide(); + } +} + +void BacklogMenu::Render() { + if (State == Hidden) return; + + const float opacity = FadeAnimation.Progress; + const glm::vec4 tint(1.0f, 1.0f, 1.0f, opacity); + + Renderer->DrawQuad(RectF(0.0f, 0.0f, Profile::Game::DesignWidth, + Profile::Game::DesignHeight), + glm::vec4(0.0f, 0.0f, 0.0f, ShadeOpacity * opacity)); + Renderer->DrawSprite(BacklogBackground, PanelPos, tint); + + if (CurrentlyFocusedElement && + RenderingBounds.Intersects(CurrentlyFocusedElement->Bounds)) { + const RectF& b = CurrentlyFocusedElement->Bounds; + Renderer->DrawQuad( + RectF(HighlightBounds.X, b.Y - EntryHighlightPadding, + HighlightBounds.Width, b.Height + 2 * EntryHighlightPadding), + glm::vec4(HighlightColor, HighlightOpacity * opacity)); + } + + Renderer->EnableScissor(); + Renderer->SetScissorRect(RenderingBounds); + for (auto& entry : Entries) { + if (!entry.Bounds.Intersects(RenderingBounds)) continue; + entry.Tint = tint; + entry.Render(); + } + Renderer->DisableScissor(); + + MainScrollbar.Tint = tint; + MainScrollbar.Render(); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/backlogmenu.h b/src/games/sg0/backlogmenu.h new file mode 100644 index 000000000..4bc794db0 --- /dev/null +++ b/src/games/sg0/backlogmenu.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../ui/backlogmenu.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 backlog (backlog.dds). _Startup_win opens it with +// SW_SYSSUBMENUNO 1, sets SW_SYSSUBMENUCT to 32 at once and fades +// SW_SYSSUBMENUALPHA 0..256 in steps of 16 (and back down when closing), so +// visibility and fade follow the alpha. +class BacklogMenu : public UI::BacklogMenu { + public: + void Hide() override; + void Render() override; + + protected: + void UpdateVisibility() override; + + private: + int LastAlpha = 0; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/clearlistmenu.cpp b/src/games/sg0/clearlistmenu.cpp new file mode 100644 index 000000000..f98214079 --- /dev/null +++ b/src/games/sg0/clearlistmenu.cpp @@ -0,0 +1,79 @@ +#include "clearlistmenu.h" + +#include + +#include "../../profile/games/sg0/clearlistmenu.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::ClearListMenu; +using namespace Impacto::Profile::ScriptVars; + +namespace { +constexpr int SubMenuNo = 7; +} // namespace + +// The script handles Back itself; the focus only keeps the title's items +// from taking the key presses meanwhile. +void ClearListMenu::Show() { + if (State == Shown || State == Showing) return; + State = Showing; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; + IsFocused = true; +} + +void ClearListMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +float ClearListMenu::Alpha() const { + if (ScrWork[SW_SYSSUBMENUNO] != SubMenuNo) return 0.0f; + return std::clamp(ScrWork[SW_SYSSUBMENUCT] / static_cast(CountMax), + 0.0f, 1.0f); +} + +void ClearListMenu::Update(float dt) { + const float alpha = Alpha(); + if (alpha > 0.0f && State != Shown) { + Show(); + if (alpha >= 1.0f) State = Shown; + } else if (alpha <= 0.0f && State != Hidden) { + Hide(); + State = Hidden; + } +} + +void ClearListMenu::Render() { + if (State == Hidden) return; + const glm::vec4 tint(1.0f, 1.0f, 1.0f, Alpha()); + Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), tint); + for (int i = 0; i < EndingCount; i++) { + if (!GetFlag(EndingFlags[i])) continue; + Renderer->DrawSprite(TubeSprites[i], TubePositions[i], tint); + Renderer->DrawSprite(LabelSprites[i], LabelPositions[i], tint); + } + Renderer->DrawSprite(GuideSprite, GuidePos, tint); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/clearlistmenu.h b/src/games/sg0/clearlistmenu.h new file mode 100644 index 000000000..b0262f8b4 --- /dev/null +++ b/src/games/sg0/clearlistmenu.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../ui/menu.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 EXTRA > CLEAR LIST (clearlist.dds): six Nixie tubes, one per +// ending, lit with the ending's character and title once its clear flag +// (801-806, set by CLRFLG.scx) is on. _Startup_win shows it with +// SW_SYSSUBMENUNO 7 and SW_SYSSUBMENUCT counting 0..64, and closes it itself +// on Back (script key 6 or a right click). +class ClearListMenu : public Menu { + public: + void Show() override; + void Hide() override; + void Update(float dt) override; + void Render() override; + + private: + float Alpha() const; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/cutin.cpp b/src/games/sg0/cutin.cpp new file mode 100644 index 000000000..334be1c5c --- /dev/null +++ b/src/games/sg0/cutin.cpp @@ -0,0 +1,51 @@ +#include "cutin.h" + +#include + +#include "../../background2d.h" +#include "../../mem.h" +#include "../../util.h" +#include "../../profile/game.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" + +namespace Impacto { +namespace SG0 { + +using namespace Impacto::Profile::ScriptVars; + +namespace { +constexpr int SlotStride = 20; +} // namespace + +void RenderCutins(const int layer) { + if (SW_CUTIN1 < 0) return; + const float scale = Profile::Game::DesignWidth / 1280.0f; + + for (size_t slot = 0; slot < Cutins.size(); slot++) { + const CutinSlot& cutin = Cutins[slot]; + if (!cutin.Enabled || cutin.BufferMask <= 0) continue; + const int base = SW_CUTIN1 + SlotStride * static_cast(slot); + if (ScrWork[base + 10] != layer) continue; + const int alpha = std::clamp(ScrWork[base + 9], 0, 256); + if (alpha == 0) continue; + + const auto bg = + Backgrounds2D.find(ScrWork[SW_BG1SURF + GetBufferId(cutin.BufferMask)]); + if (bg == Backgrounds2D.end() || bg->second == nullptr || + bg->second->Status != LoadStatus::Loaded) { + continue; + } + + Sprite region = bg->second->BgSprite; + region.Bounds = RectF(ScrWork[base + 1] * scale, ScrWork[base + 2] * scale, + ScrWork[base + 3] * scale, ScrWork[base + 4] * scale); + const RectF dest(ScrWork[base + 5] * scale, ScrWork[base + 6] * scale, + ScrWork[base + 7] * scale, ScrWork[base + 8] * scale); + Renderer->DrawSprite(region, dest, + glm::vec4(1.0f, 1.0f, 1.0f, alpha / 256.0f)); + } +} + +} // namespace SG0 +} // namespace Impacto diff --git a/src/games/sg0/cutin.h b/src/games/sg0/cutin.h new file mode 100644 index 000000000..6d715e68c --- /dev/null +++ b/src/games/sg0/cutin.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace Impacto { +namespace SG0 { + +// STEINS;GATE 0 cut-ins: a region of a background shown enlarged in another +// rectangle (SG0_01_08 zooms into the EV_C11 flashback). SetCutin(slot, +// enable, bufferMask, bufferMask) switches a slot on for the background in +// that script buffer; the macros in macrosys write the slot's ScrWork block +// at SW_CUTIN1 + 20 * slot: +1..+4 source and +5..+8 destination rectangles +// (1280x720 units), +9 alpha (0..256, faded by the macros), +10 the layer +// it is drawn on. +struct CutinSlot { + bool Enabled = false; + int BufferMask = 0; +}; + +inline std::array Cutins; + +void RenderCutins(int layer); + +} // namespace SG0 +} // namespace Impacto diff --git a/src/games/sg0/fullwidth.h b/src/games/sg0/fullwidth.h new file mode 100644 index 000000000..97aa8cc22 --- /dev/null +++ b/src/games/sg0/fullwidth.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../../profile/dialogue.h" +#include "../../text/fonts/font.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// SG0's font spaces its full-width Latin glyphs for text; the half-width ones +// overlap. Map printable ASCII to U+FF01..FF5E (space to U+3000) as UTF-8. +inline std::string FullWidth(std::string_view ascii) { + std::string out; + for (char c : ascii) { + const uint32_t cp = c == ' ' ? 0x3000 + : (c > ' ' && c < 0x7F) ? 0xFF01 + (c - '!') + : static_cast(c); + if (cp < 0x80) { + out += static_cast(cp); + } else { + out += static_cast(0xE0 | (cp >> 12)); + out += static_cast(0x80 | ((cp >> 6) & 0x3F)); + out += static_cast(0x80 | (cp & 0x3F)); + } + } + return out; +} + +// Draws text shifted by offset. The font culls glyphs against the screen +// using their own positions, so text laid out off screen is moved into place +// first instead of being drawn with a translation. +inline void DrawTextAt(std::span text, + glm::vec2 offset, float opacity, + Fonts::RendererOutlineMode outlineMode) { + std::vector moved(text.begin(), text.end()); + for (ProcessedTextGlyph& glyph : moved) glyph.Move(offset); + Profile::Dialogue::DialogueFont->DrawProcessedText(moved, opacity, opacity, + outlineMode); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/helpmenu.cpp b/src/games/sg0/helpmenu.cpp new file mode 100644 index 000000000..33b19a97d --- /dev/null +++ b/src/games/sg0/helpmenu.cpp @@ -0,0 +1,106 @@ +#include "helpmenu.h" + +#include + +#include "../../profile/games/sg0/helpmenu.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::HelpMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +namespace { +constexpr int SubMenuNo = 11; +constexpr int SF_HELP_CLOSED = 1830; +} // namespace + +void HelpMenu::Show() { + if (State == Shown || State == Showing) return; + State = Showing; + Page = 0; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; +} + +void HelpMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +void HelpMenu::UpdateInput(float dt) { + if (!IsFocused || GetFlag(SF_HELP_CLOSED)) return; + const int pages = static_cast(PageSprites.size()); + int next = Page; + if (PADinputButtonWentDown & (PAD1LEFT | PAD1L1) || + Input::MouseWheelDeltaY > 0) { + next = std::max(Page - 1, 0); + } else if (PADinputButtonWentDown & (PAD1RIGHT | PAD1R1 | PAD1A) || + Input::MouseWheelDeltaY < 0 || + Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + next = std::min(Page + 1, pages - 1); + } + if (next != Page) { + Page = next; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + if (PADinputButtonWentDown & PAD1B || + Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]) { + SetFlag(SF_HELP_CLOSED, true); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 3, false, 0); + } +} + +void HelpMenu::Update(float dt) { + const int count = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo ? ScrWork[SW_SYSSUBMENUCT] : 0; + if (count > LastSubMenuCount && count > 0) { + Show(); + } else if (count < LastSubMenuCount) { + Hide(); + } + LastSubMenuCount = count; + + if (State == Showing && count >= CountMax) { + State = Shown; + IsFocused = UI::FocusedMenu == this; + } else if (State == Hiding && count <= 0) { + State = Hidden; + } + if (State == Shown) UpdateInput(dt); +} + +void HelpMenu::Render() { + if (State == Hidden || PageSprites.empty()) return; + const float alpha = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo + ? std::clamp(ScrWork[SW_SYSSUBMENUCT] / static_cast(CountMax), + 0.0f, 1.0f) + : 0.0f; + Renderer->DrawSprite(PageSprites[Page], glm::vec2(0.0f), + glm::vec4(1.0f, 1.0f, 1.0f, alpha)); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/helpmenu.h b/src/games/sg0/helpmenu.h new file mode 100644 index 000000000..9cb13ca08 --- /dev/null +++ b/src/games/sg0/helpmenu.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../../ui/menu.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 help: the operating-instruction pages from manual.mpk. +// _Startup_win opens it with SW_SYSSUBMENUNO 11 and SW_SYSSUBMENUCT counting +// to 32, then calls Help(1) every frame until flag 1830 (closed) is set. +class HelpMenu : public Menu { + public: + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + + private: + int Page = 0; + int LastSubMenuCount = 0; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/librarymenu.cpp b/src/games/sg0/librarymenu.cpp new file mode 100644 index 000000000..8753a9e2a --- /dev/null +++ b/src/games/sg0/librarymenu.cpp @@ -0,0 +1,767 @@ +#include "librarymenu.h" + +#include +#include + +#include + +#include "fullwidth.h" +#include "../../profile/games/sg0/librarymenu.h" +#include "../../profile/data/savesystem.h" +#include "../../profile/dialogue.h" +#include "../../profile/game.h" +#include "../../profile/scriptvars.h" +#include "../../data/savesystem.h" +#include "../../background2d.h" +#include "../../renderer/renderer.h" +#include "../../text/text.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../util.h" +#include "../../inputsystem.h" +#include "../../vm/vm.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" +#include "../../audio/audiochannel.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::LibraryMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +namespace { +constexpr int SubMenuCg = 8; +constexpr int SubMenuMovie = 9; +constexpr int SubMenuSound = 10; +constexpr int NoMovie = 255; +constexpr float ZoomStep = 1.25f; + +int SubMenuFor(int library) { + return library == 0 ? SubMenuCg : library == 1 ? SubMenuSound : SubMenuMovie; +} + +void PlaySe(int id) { + Audio::PlayInGroup(Audio::ACG_SE, "sysse", id, false, 0); +} + +std::vector LayoutSystemString(int strNum, float size) { + const uint32_t addr = Vm::ScriptGetStrAddress(SystemScriptBuffer, strNum); + Vm::Sc3Stream stream(&Vm::ScriptBuffers[SystemScriptBuffer][addr]); + return TextLayoutPlainLine(stream, 255, *Profile::Dialogue::DialogueFont, + size, {TextColor, 0}, 1.0f, glm::vec2(0.0f), + TextAlignment::Left); +} + +std::vector LayoutAscii(std::string_view text, float size) { + return TextLayoutPlainString( + FullWidth(text), *Profile::Dialogue::DialogueFont, size, {TextColor, 0}, + 1.0f, glm::vec2(0.0f), TextAlignment::Left); +} + +// Draws glyphs laid out at the origin at pos, recoloured. +void DrawText(std::span text, glm::vec2 pos, + float opacity, uint32_t color) { + std::vector moved(text.begin(), text.end()); + for (ProcessedTextGlyph& glyph : moved) { + glyph.Move(pos); + glyph.Colors = {color, 0}; + } + Profile::Dialogue::DialogueFont->DrawProcessedText( + moved, opacity, opacity, Fonts::RendererOutlineMode::None); +} + +std::string FormatTime(float seconds) { + const int total = std::max(0, static_cast(seconds)); + return fmt::format("{}:{:02}", total / 60, total % 60); +} + +Background2D* CgBackground() { + return Backgrounds2D[ScrWork[SW_BG1SURF + GetBufferId(CgLoadBuffer)]]; +} +} // namespace + +void LibraryMenu::Init() { + Active = true; + ScrWork[SW_MOVIEMODE_CUR] = NoMovie; + SetFlag(SF_ALBUMEND, false); + SetFlag(SF_ALBUMLOAD, false); + SetFlag(SF_ALBUMLOAD_COMPLETE, false); + ViewerOpen = false; + PlayingTrack = -1; + switch (ScrWork[SW_SYSSUBMENUNO]) { + case SubMenuSound: + Current = Library::Sound; + break; + case SubMenuMovie: + Current = Library::Movie; + break; + default: + Current = Library::Cg; + break; + } + if (!TextsLaidOut) LayoutTexts(); +} + +void LibraryMenu::LayoutTexts() { + const int tracks = static_cast(TrackBgmIds.size()); + const float titleWidth = ListArtistX - ListTitleX - 24.0f; + for (int i = 0; i < tracks; i++) { + TrackTitles.push_back( + LayoutSystemString(TrackStringBase + 2 * i, ListFontSize)); + SquishText(TrackTitles.back(), titleWidth, 0.0f); + TrackArtists.push_back( + LayoutSystemString(TrackStringBase + 2 * i + 1, ListFontSize)); + } + LockedTrackTitle = LayoutSystemString(LockedTrackString, ListFontSize); + + const float movieWidth = MovieCellPitch.x - 24.0f; + for (size_t i = 0; i < MovieIds.size(); i++) { + MovieTitles.push_back(LayoutSystemString( + MovieStringBase + 2 * static_cast(i), MovieFontSize)); + SquishText(MovieTitles.back(), movieWidth, 0.0f); + MovieArtists.push_back(LayoutSystemString( + MovieStringBase + 2 * static_cast(i) + 1, MovieFontSize * 0.8f)); + } + TextsLaidOut = true; +} + +void LibraryMenu::Show() { + if (State == Shown || State == Showing) return; + State = Showing; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; +} + +void LibraryMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + IsFocused = false; + if (ViewerOpen) CloseViewer(); + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +bool LibraryMenu::AllEndingsCleared() const { + return std::ranges::all_of(EndingFlags, + [](int flag) { return GetFlag(flag); }); +} + +int LibraryMenu::CgCount() const { + return static_cast(ThumbnailSprites.size()); +} + +bool LibraryMenu::IsCgVariationUnlocked(int entry, int variation) const { + if (Profile::SaveSystem::AlbumEvData[entry][variation] == 0xFFFF) + return false; + if (std::ranges::find(AllClearCgs, entry) != AllClearCgs.end()) { + return AllEndingsCleared(); + } + return SaveSystem::GetEVVariationIsUnlocked(entry, variation); +} + +bool LibraryMenu::IsCgUnlocked(int entry) const { + for (int v = 0; v < Profile::SaveSystem::MaxAlbumSubEntries; v++) { + if (Profile::SaveSystem::AlbumEvData[entry][v] == 0xFFFF) break; + if (IsCgVariationUnlocked(entry, v)) return true; + } + return false; +} + +bool LibraryMenu::IsMovieUnlocked(int movie) const { + const std::vector& lines = MovieUnlockLines[movie]; + for (size_t i = 0; i + 1 < lines.size(); i += 2) { + if (SaveSystem::IsLineRead(lines[i], lines[i + 1])) return true; + } + return false; +} + +bool LibraryMenu::IsTrackUnlocked(int track) const { + if (SaveSystem::GetBgmFlag(TrackBgmIds[track])) return true; + if (TrackBgmAltIds[track] >= 0 && + SaveSystem::GetBgmFlag(TrackBgmAltIds[track])) + return true; + return TrackMovieUnlocks[track] >= 0 && + IsMovieUnlocked(TrackMovieUnlocks[track]); +} + +void LibraryMenu::Update(float dt) { + Time += dt; + const int subMenu = ScrWork[SW_SYSSUBMENUNO]; + const bool ours = subMenu == SubMenuCg || subMenu == SubMenuMovie || + subMenu == SubMenuSound; + const int count = Active && ours ? ScrWork[SW_SYSSUBMENUCT] : 0; + if (count > 0 && (State == Hidden || State == Hiding) && + !GetFlag(SF_ALBUMEND)) { + Show(); + } else if ((State == Shown || State == Showing) && + (count < LastSubMenuCount || count == 0 || GetFlag(SF_ALBUMEND))) { + Hide(); + } + LastSubMenuCount = count; + + if (State == Showing && count >= CountMax) { + State = Shown; + IsFocused = UI::FocusedMenu == this; + } else if (State == Hiding && count <= 0) { + State = Hidden; + Active = false; + } + + if (ViewerOpen && !ViewerLoaded && GetFlag(SF_ALBUMLOAD_COMPLETE)) { + const Background2D* bg = CgBackground(); + if (bg != nullptr && bg->Status == LoadStatus::Loaded) { + const glm::vec2 screen(Profile::Game::DesignWidth, + Profile::Game::DesignHeight); + ViewerSize = + glm::vec2(bg->BgSprite.ScaledWidth(), bg->BgSprite.ScaledHeight()); + ViewerCoverScale = + std::max(screen.x / ViewerSize.x, screen.y / ViewerSize.y); + ViewerContainScale = + std::min(screen.x / ViewerSize.x, screen.y / ViewerSize.y); + ViewerScale = ViewerCoverScale; + ViewerPan = + glm::vec2((screen.x - ViewerSize.x * ViewerScale) / 2.0f, 0.0f); + ClampViewerPan(); + ViewerLoaded = true; + } + } + + if (Active) UpdatePlayback(); + if (State == Shown && MainCalled) UpdateInput(dt); + MainCalled = false; +} + +void LibraryMenu::SetLibrary(Library library) { + if (library == Current) return; + Current = library; + ScrWork[SW_SYSSUBMENUNO] = SubMenuFor(static_cast(library)); + PlaySe(1); +} + +void LibraryMenu::Back() { + SetFlag(SF_ALBUMEND, true); + PlayingTrack = -1; + PlaySe(3); +} + +void LibraryMenu::UpdateInput(float dt) { + if (!IsFocused || GetFlag(SF_ALBUMEND) || + ScrWork[SW_MOVIEMODE_CUR] != NoMovie) { + return; + } + if (ViewerOpen) { + UpdateViewerInput(dt); + return; + } + if (PADinputButtonWentDown & PAD1B || + Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]) { + Back(); + return; + } + const int library = static_cast(Current); + if (PADinputButtonWentDown & PAD1L1) { + SetLibrary( + static_cast((library + LibraryCount - 1) % LibraryCount)); + return; + } + if (PADinputButtonWentDown & PAD1R1) { + SetLibrary(static_cast((library + 1) % LibraryCount)); + return; + } + UpdateTabInput(); + switch (Current) { + case Library::Cg: + UpdateCgInput(dt); + break; + case Library::Sound: + UpdateSoundInput(dt); + break; + case Library::Movie: + UpdateMovieInput(dt); + break; + } +} + +void LibraryMenu::UpdateTabInput() { + if (!Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) return; + // Tab 0 is CLEAR LIST, a separate EXTRA screen. + for (int tab = 1; tab < TabCount; tab++) { + const RectF bounds(TabPos.x, TabPos.y + tab * TabPitch, + TabSprites[tab].ScaledWidth(), + TabSprites[tab].ScaledHeight()); + if (bounds.ContainsPoint(Input::CurMousePos)) { + SetLibrary(static_cast(tab - 1)); + Input::MouseButtonWentDown[SDL_BUTTON_LEFT] = false; + return; + } + } +} + +RectF LibraryMenu::CgCell(int entry) const { + const int row = entry / CgColumns - CgTopRow; + const int col = entry % CgColumns; + return RectF(CgGridPos.x + col * CgCellPitch.x, + CgGridPos.y + row * CgCellPitch.y, NoDataSprite.ScaledWidth(), + NoDataSprite.ScaledHeight()); +} + +void LibraryMenu::UpdateCgInput(float dt) { + const int count = CgCount(); + int next = CgCursor; + if (PADinputButtonRepeatAccelDown & PAD1LEFT) + next = std::max(CgCursor - 1, 0); + if (PADinputButtonRepeatAccelDown & PAD1RIGHT) + next = std::min(CgCursor + 1, count - 1); + if (PADinputButtonRepeatAccelDown & PAD1UP && CgCursor >= CgColumns) + next = CgCursor - CgColumns; + if (PADinputButtonRepeatAccelDown & PAD1DOWN && + CgCursor / CgColumns < (count - 1) / CgColumns) + next = std::min(CgCursor + CgColumns, count - 1); + if (Input::MouseWheelDeltaY > 0 && CgCursor >= CgColumns) + next = CgCursor - CgColumns; + if (Input::MouseWheelDeltaY < 0 && + CgCursor / CgColumns < (count - 1) / CgColumns) + next = std::min(CgCursor + CgColumns, count - 1); + + bool clicked = false; + for (int i = CgTopRow * CgColumns; + i < std::min(count, (CgTopRow + CgRows) * CgColumns); i++) { + if (!CgCell(i).ContainsPoint(Input::CurMousePos)) continue; + if (Input::PrevMousePos != Input::CurMousePos) next = i; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + next = i; + clicked = true; + } + } + + if (next != CgCursor) { + CgCursor = next; + PlaySe(1); + } + const int row = CgCursor / CgColumns; + if (row < CgTopRow) CgTopRow = row; + if (row >= CgTopRow + CgRows) CgTopRow = row - CgRows + 1; + + if (PADinputButtonWentDown & PAD1A || clicked) { + if (IsCgUnlocked(CgCursor)) { + OpenViewer(CgCursor); + PlaySe(2); + } + } +} + +void LibraryMenu::OpenViewer(int entry) { + ViewerVariations.clear(); + for (int v = 0; v < Profile::SaveSystem::MaxAlbumSubEntries; v++) { + if (Profile::SaveSystem::AlbumEvData[entry][v] == 0xFFFF) break; + if (IsCgVariationUnlocked(entry, v)) ViewerVariations.push_back(v); + } + if (ViewerVariations.empty()) return; + ViewerEntry = entry; + ViewerVariation = 0; + ViewerOpen = true; + ViewerGuide = true; + RequestCg(); +} + +void LibraryMenu::RequestCg() { + ScrWork[SW_ALBUM_LOADFILE] = + Profile::SaveSystem::AlbumData[ViewerEntry] + [ViewerVariations[ViewerVariation]][0]; + ScrWork[SW_ALBUM_LOADBUF] = CgLoadBuffer; + SetFlag(SF_ALBUMLOAD_COMPLETE, false); + SetFlag(SF_ALBUMLOAD, true); + ViewerLoaded = false; +} + +void LibraryMenu::CloseViewer() { + SetFlag(SF_ALBUMLOAD, false); + SetFlag(SF_ALBUMLOAD_COMPLETE, false); + ViewerOpen = false; + ViewerLoaded = false; +} + +void LibraryMenu::ClampViewerPan() { + const glm::vec2 screen(Profile::Game::DesignWidth, + Profile::Game::DesignHeight); + const glm::vec2 size = ViewerSize * ViewerScale; + for (int axis = 0; axis < 2; axis++) { + ViewerPan[axis] = + size[axis] <= screen[axis] + ? (screen[axis] - size[axis]) / 2.0f + : std::clamp(ViewerPan[axis], screen[axis] - size[axis], 0.0f); + } +} + +void LibraryMenu::UpdateViewerInput(float dt) { + if (PADinputButtonWentDown & PAD1B || + Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]) { + CloseViewer(); + PlaySe(3); + return; + } + if (PADinputButtonWentDown & PAD1X) ViewerGuide = !ViewerGuide; + if (!ViewerLoaded) return; + + if (PADinputButtonWentDown & PAD1A || + Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + if (ViewerVariation + 1 < ViewerVariations.size()) { + ViewerVariation++; + RequestCg(); + PlaySe(1); + } else { + CloseViewer(); + PlaySe(3); + } + return; + } + + float zoom = 1.0f; + if (PADinputButtonWentDown & PAD1L2) zoom = 1.0f / ZoomStep; + if (PADinputButtonWentDown & PAD1R2) zoom = ZoomStep; + if (zoom != 1.0f) { + const float scale = std::clamp(ViewerScale * zoom, ViewerContainScale, + ViewerCoverScale * CgZoomMax); + const glm::vec2 center(Profile::Game::DesignWidth / 2.0f, + Profile::Game::DesignHeight / 2.0f); + ViewerPan = center - (center - ViewerPan) * (scale / ViewerScale); + ViewerScale = scale; + } + + glm::vec2 move(0.0f); + if (PADinputButtonIsDown & PAD1LEFT) move.x += 1.0f; + if (PADinputButtonIsDown & PAD1RIGHT) move.x -= 1.0f; + if (PADinputButtonIsDown & PAD1UP) move.y += 1.0f; + if (PADinputButtonIsDown & PAD1DOWN) move.y -= 1.0f; + ViewerPan += move * CgScrollSpeed * dt; + ViewerPan.y += Input::MouseWheelDeltaY * CgScrollSpeed * 0.1f; + ClampViewerPan(); +} + +void LibraryMenu::UpdateSoundInput(float dt) { + const int count = static_cast(TrackBgmIds.size()); + int next = TrackCursor; + if (PADinputButtonRepeatAccelDown & PAD1UP) + next = std::max(TrackCursor - 1, 0); + if (PADinputButtonRepeatAccelDown & PAD1DOWN) + next = std::min(TrackCursor + 1, count - 1); + if (PADinputButtonRepeatAccelDown & PAD1LEFT) + next = std::max(TrackCursor - ListVisibleRows, 0); + if (PADinputButtonRepeatAccelDown & PAD1RIGHT) + next = std::min(TrackCursor + ListVisibleRows, count - 1); + if (Input::MouseWheelDeltaY != 0) { + next = std::clamp(TrackCursor - static_cast(Input::MouseWheelDeltaY), + 0, count - 1); + } + + bool clicked = false; + const float headerHeight = ListHeaderSprite.ScaledHeight(); + for (int row = 0; row < ListVisibleRows && TrackTopRow + row < count; row++) { + const RectF bounds(ListBounds.X, + ListBounds.Y + headerHeight + row * ListRowHeight, + ListBounds.Width, ListRowHeight); + if (!bounds.ContainsPoint(Input::CurMousePos)) continue; + if (Input::PrevMousePos != Input::CurMousePos) next = TrackTopRow + row; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + next = TrackTopRow + row; + clicked = true; + } + } + + if (next != TrackCursor) { + TrackCursor = next; + PlaySe(1); + } + if (TrackCursor < TrackTopRow) TrackTopRow = TrackCursor; + if (TrackCursor >= TrackTopRow + ListVisibleRows) + TrackTopRow = TrackCursor - ListVisibleRows + 1; + + if ((PADinputButtonWentDown & PAD1A || clicked) && + IsTrackUnlocked(TrackCursor)) { + PlayTrack(TrackCursor); + } + if (PADinputButtonWentDown & PAD1X) StopTrack(); + if (PADinputButtonWentDown & PAD1Y) { + RepeatMode = (RepeatMode + 1) % 4; + PlaySe(1); + } +} + +void LibraryMenu::PlayTrack(int track) { + const int bgm = TrackBgmIds[track]; + Audio::Channels[Audio::AC_BGM0]->Play("bgm", bgm, RepeatMode == 0, 0.0f); + // The library's closing BGMplay (the title theme) only plays when the + // requested track differs, as for the scripts' own BGMplay. + ScrWork[SW_BGMREQNO] = bgm; + PlayingTrack = track; +} + +void LibraryMenu::StopTrack() { + Audio::Channels[Audio::AC_BGM0]->Stop(0.5f); + ScrWork[SW_BGMREQNO] = 0xFFFF; + PlayingTrack = -1; +} + +void LibraryMenu::UpdatePlayback() { + if (PlayingTrack < 0 || RepeatMode == 0) return; + if (Audio::Channels[Audio::AC_BGM0]->GetState() != Audio::ACS_Stopped) return; + + const int count = static_cast(TrackBgmIds.size()); + int next = -1; + if (RepeatMode == 3) { + std::vector unlocked; + for (int t = 0; t < count; t++) { + if (t != PlayingTrack && IsTrackUnlocked(t)) unlocked.push_back(t); + } + next = unlocked.empty() + ? PlayingTrack + : unlocked[CALCrnd(static_cast(unlocked.size()))]; + } else { + for (int step = 1; step <= count; step++) { + int t = PlayingTrack + step; + if (t >= count) { + if (RepeatMode == 1) break; + t -= count; + } + if (IsTrackUnlocked(t)) { + next = t; + break; + } + } + } + if (next < 0) { + PlayingTrack = -1; + ScrWork[SW_BGMREQNO] = 0xFFFF; + return; + } + PlayTrack(next); +} + +RectF LibraryMenu::MovieCell(int movie) const { + const Sprite& thumb = MovieThumbnailSprites[movie]; + const int row = movie / MovieColumns; + const int col = movie % MovieColumns; + return RectF(MovieGridPos.x + col * MovieCellPitch.x, + MovieGridPos.y + row * MovieCellPitch.y, + thumb.ScaledWidth() * MovieThumbnailScale, + thumb.ScaledHeight() * MovieThumbnailScale); +} + +void LibraryMenu::UpdateMovieInput(float dt) { + const int count = static_cast(MovieIds.size()); + int next = MovieCursor; + if (PADinputButtonRepeatAccelDown & PAD1LEFT) + next = std::max(MovieCursor - 1, 0); + if (PADinputButtonRepeatAccelDown & PAD1RIGHT) + next = std::min(MovieCursor + 1, count - 1); + if (PADinputButtonRepeatAccelDown & PAD1UP && MovieCursor >= MovieColumns) + next = MovieCursor - MovieColumns; + if (PADinputButtonRepeatAccelDown & PAD1DOWN && + MovieCursor + MovieColumns < count) + next = MovieCursor + MovieColumns; + + bool clicked = false; + for (int i = 0; i < count; i++) { + if (!MovieCell(i).ContainsPoint(Input::CurMousePos)) continue; + if (Input::PrevMousePos != Input::CurMousePos) next = i; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + next = i; + clicked = true; + } + } + if (next != MovieCursor) { + MovieCursor = next; + PlaySe(1); + } + if ((PADinputButtonWentDown & PAD1A || clicked) && + IsMovieUnlocked(MovieCursor)) { + ScrWork[SW_MOVIEMODE_CUR] = MovieIds[MovieCursor]; + PlaySe(2); + } +} + +void LibraryMenu::Render() { + if (State == Hidden || ScrWork[SW_MOVIEMODE_CUR] != NoMovie) return; + const float alpha = std::clamp( + ScrWork[SW_SYSSUBMENUCT] / static_cast(CountMax), 0.0f, 1.0f); + const glm::vec4 tint(1.0f, 1.0f, 1.0f, alpha); + if (ViewerOpen) { + RenderViewer(tint); + return; + } + Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), tint); + switch (Current) { + case Library::Cg: + RenderCg(tint); + break; + case Library::Sound: + RenderSound(tint); + break; + case Library::Movie: + RenderMovie(tint); + break; + } + RenderTabs(tint); + if (Current != Library::Sound) { + Renderer->DrawSprite(TitleSprites[static_cast(Current)], TitlePos, + tint); + } + RenderGuide(Current == Library::Cg ? 0 + : Current == Library::Sound ? 2 + : 3, + tint); +} + +void LibraryMenu::RenderTabs(glm::vec4 tint) { + const int selected = static_cast(Current) + 1; + for (int tab = 0; tab < TabCount; tab++) { + const Sprite& sprite = + tab == selected ? TabSelectedSprites[tab] : TabSprites[tab]; + Renderer->DrawSprite(sprite, TabPos + glm::vec2(0.0f, tab * TabPitch), + tint); + } +} + +void LibraryMenu::RenderGuide(int guide, glm::vec4 tint) { + const Sprite& sprite = GuideSprites[guide]; + Renderer->DrawSprite( + sprite, glm::vec2(GuideRight - sprite.ScaledWidth(), GuideY), tint); +} + +void LibraryMenu::RenderCg(glm::vec4 tint) { + const int count = CgCount(); + for (int i = CgTopRow * CgColumns; + i < std::min(count, (CgTopRow + CgRows) * CgColumns); i++) { + const RectF cell = CgCell(i); + const glm::vec2 pos(cell.X, cell.Y); + const bool selected = i == CgCursor; + if (!IsCgUnlocked(i)) { + Renderer->DrawSprite(selected ? NoDataSelectedSprite : NoDataSprite, pos, + tint); + continue; + } + Renderer->DrawSprite(ThumbnailSprites[i], pos + ThumbnailOffset, tint); + if (selected) { + glm::vec4 glow = tint; + glow.a *= 0.35f + 0.25f * std::sin(Time * 4.0f); + Renderer->DrawSprite(SelectedGlowSprite, pos + ThumbnailOffset, glow); + Renderer->DrawSprite(SelectedFrameSprite, pos, tint); + } + } + + const int rows = (count + CgColumns - 1) / CgColumns; + const int maxTop = std::max(rows - CgRows, 1); + const float travel = + ScrollBottom - ScrollTop - ScrollThumbSprite.ScaledHeight(); + Renderer->DrawSprite( + ScrollThumbSprite, + glm::vec2(ScrollX, + ScrollTop + travel * CgTopRow / static_cast(maxTop)), + tint); +} + +void LibraryMenu::RenderViewer(glm::vec4 tint) { + Renderer->DrawQuad(RectF(0.0f, 0.0f, Profile::Game::DesignWidth, + Profile::Game::DesignHeight), + glm::vec4(0.0f, 0.0f, 0.0f, tint.a)); + if (ViewerLoaded) { + const glm::vec2 size = ViewerSize * ViewerScale; + Renderer->DrawSprite(CgBackground()->BgSprite, + RectF(ViewerPan.x, ViewerPan.y, size.x, size.y), tint); + } + if (ViewerGuide) RenderGuide(1, tint); +} + +void LibraryMenu::RenderSound(glm::vec4 tint) { + Renderer->DrawSprite(PlayerSprite, PlayerPos, tint); + + const int count = static_cast(TrackBgmIds.size()); + const float headerHeight = ListHeaderSprite.ScaledHeight(); + Renderer->EnableScissor(); + Renderer->SetScissorRect(ListBounds); + Renderer->DrawSprite(ListHeaderSprite, glm::vec2(ListBounds.X, ListBounds.Y), + tint); + for (int row = 0; row < ListVisibleRows && TrackTopRow + row < count; row++) { + const int track = TrackTopRow + row; + const float y = ListBounds.Y + headerHeight + row * ListRowHeight; + const bool selected = track == TrackCursor; + const bool unlocked = IsTrackUnlocked(track); + Renderer->DrawSprite( + selected ? ListSelectedSprite : ListRowSprites[track % 2], + glm::vec2(ListBounds.X, y), tint); + const uint32_t color = selected ? SelectedTextColor + : unlocked ? TextColor + : LockedTextColor; + const float textY = y + (ListRowHeight - ListFontSize) / 2.0f; + if (track == PlayingTrack) { + const float icon = ListRowHeight - 20.0f; + Renderer->DrawSprite( + PlayIconSprite, + RectF(ListBounds.X + ListTrackX, y + 10.0f, icon, icon), tint); + } else { + DrawText(LayoutAscii(fmt::format("{:02}", track + 1), ListFontSize), + glm::vec2(ListBounds.X + ListTrackX, textY), tint.a, color); + } + DrawText(unlocked ? TrackTitles[track] : LockedTrackTitle, + glm::vec2(ListBounds.X + ListTitleX, textY), tint.a, color); + if (unlocked) { + DrawText(TrackArtists[track], + glm::vec2(ListBounds.X + ListArtistX, textY), tint.a, color); + } + } + Renderer->DisableScissor(); + + if (PlayingTrack >= 0) { + const auto& channel = Audio::Channels[Audio::AC_BGM0]; + DrawText(TrackTitles[PlayingTrack], NowPlayingPos, tint.a, TextColor); + const std::string time = FormatTime(channel->PositionInSeconds()) + " / " + + FormatTime(channel->DurationInSeconds()); + DrawText(LayoutAscii(time, NowPlayingFontSize), + NowPlayingPos + glm::vec2(0.0f, NowPlayingFontSize + 8.0f), tint.a, + TextColor); + } + Renderer->DrawSprite(RepeatModeSprites[RepeatMode], RepeatModePos, tint); +} + +void LibraryMenu::RenderMovie(glm::vec4 tint) { + for (int i = 0; i < static_cast(MovieIds.size()); i++) { + const RectF cell = MovieCell(i); + const bool unlocked = IsMovieUnlocked(i); + Renderer->DrawSprite(unlocked ? MovieThumbnailSprites[i] : NoDataSprite, + cell, tint); + if (i == MovieCursor) { + Renderer->DrawSprite( + unlocked ? SelectedFrameSprite : NoDataSelectedSprite, + RectF(cell.X - 4.0f, cell.Y - 4.0f, cell.Width + 8.0f, + cell.Height + 8.0f), + tint); + } + const uint32_t color = unlocked ? SelectedTextColor : LockedTextColor; + const auto& title = unlocked ? MovieTitles[i] : LockedTrackTitle; + const float centerX = cell.X + cell.Width / 2.0f; + const float titleY = cell.Y + cell.Height + MovieTitleOffsetY; + DrawText(title, glm::vec2(centerX - GetTextWidth(title) / 2.0f, titleY), + tint.a, color); + if (unlocked) { + DrawText(MovieArtists[i], + glm::vec2(centerX - GetTextWidth(MovieArtists[i]) / 2.0f, + titleY + MovieFontSize + 6.0f), + tint.a, color); + } + } +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/librarymenu.h b/src/games/sg0/librarymenu.h new file mode 100644 index 000000000..584130f25 --- /dev/null +++ b/src/games/sg0/librarymenu.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#include "../../ui/menu.h" +#include "../../text/processedtextglyph.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 EXTRA > CG / SOUND / MOVIE LIBRARY (exmenu.dds, exmenu2.dds, +// album_thum.dds). _Startup_win enters every library through one loop: +// SW_SYSSUBMENUNO 8 (CG), 9 (movie) or 10 (sound), Album(0), SW_SYSSUBMENUCT +// counting 0..32, then Album(1) every frame until SF_ALBUMEND (1311) is set. +// A CG is shown by asking the script's loader thread for it (SW_ALBUM_LOADFILE, +// SW_ALBUM_LOADBUF, SF_ALBUMLOAD; it answers with SF_ALBUMLOAD_COMPLETE), and +// a movie by setting SW_MOVIEMODE_CUR, which the loop plays before Album(3). +// Q/E switch libraries, as the guide bars say. +class LibraryMenu : public Menu { + public: + void Init() override; + void MainTick() { MainCalled = true; } + + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + + private: + enum class Library : int { Cg = 0, Sound = 1, Movie = 2 }; + + void SetLibrary(Library library); + void Back(); + bool AllEndingsCleared() const; + bool IsCgUnlocked(int entry) const; + bool IsCgVariationUnlocked(int entry, int variation) const; + bool IsMovieUnlocked(int movie) const; + bool IsTrackUnlocked(int track) const; + int CgCount() const; + + void UpdateCgInput(float dt); + void UpdateViewerInput(float dt); + void UpdateSoundInput(float dt); + void UpdateMovieInput(float dt); + void UpdateTabInput(); + void UpdatePlayback(); + + void OpenViewer(int entry); + void CloseViewer(); + void RequestCg(); + void ClampViewerPan(); + void PlayTrack(int track); + void StopTrack(); + + void RenderTabs(glm::vec4 tint); + void RenderGuide(int guide, glm::vec4 tint); + void RenderCg(glm::vec4 tint); + void RenderViewer(glm::vec4 tint); + void RenderSound(glm::vec4 tint); + void RenderMovie(glm::vec4 tint); + + void LayoutTexts(); + RectF CgCell(int entry) const; + RectF MovieCell(int movie) const; + + bool Active = false; + bool MainCalled = false; + int LastSubMenuCount = 0; + Library Current = Library::Cg; + float Time = 0.0f; + + int CgCursor = 0; + int CgTopRow = 0; + + bool ViewerOpen = false; + bool ViewerLoaded = false; + bool ViewerGuide = true; + int ViewerEntry = 0; + std::vector ViewerVariations; + size_t ViewerVariation = 0; + glm::vec2 ViewerSize{0.0f}; + glm::vec2 ViewerPan{0.0f}; + float ViewerScale = 1.0f; + float ViewerCoverScale = 1.0f; + float ViewerContainScale = 1.0f; + + int TrackCursor = 0; + int TrackTopRow = 0; + int PlayingTrack = -1; + int RepeatMode = 0; // 0 repeat one, 1 play through, 2 repeat all, 3 shuffle + std::vector> TrackTitles; + std::vector> TrackArtists; + std::vector LockedTrackTitle; + + int MovieCursor = 0; + std::vector> MovieTitles; + std::vector> MovieArtists; + bool TextsLaidOut = false; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/optionsmenu.cpp b/src/games/sg0/optionsmenu.cpp new file mode 100644 index 000000000..e5c14d6f2 --- /dev/null +++ b/src/games/sg0/optionsmenu.cpp @@ -0,0 +1,396 @@ +#include "optionsmenu.h" + +#include +#include +#include + +#include "../../profile/configsystem.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::OptionsMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Profile::ConfigSystem; +using namespace Impacto::Vm::Interface; +using Setting = Impacto::Profile::SG0::OptionsMenu::Setting; + +namespace { +constexpr int SubMenuNo = 5; +constexpr int SF_OPTION_CLOSED = 1830; +constexpr float SliderStep = 0.05f; +constexpr std::array Resolutions = { + glm::ivec2(1024, 576), glm::ivec2(1280, 720), glm::ivec2(1920, 1080)}; + +float ToUnit(float value, glm::vec2 bounds) { + return std::clamp((value - bounds.x) / (bounds.y - bounds.x), 0.0f, 1.0f); +} +float FromUnit(float unit, glm::vec2 bounds) { + return bounds.x + (bounds.y - bounds.x) * unit; +} +} // namespace + +OptionsMenu::OptionsMenu() { InputConfig = InputRate::RepeatFast; } + +std::vector OptionsMenu::PageRows(int page) const { + std::vector rows; + for (const Row& row : Rows) { + if (row.Page == page) rows.push_back(&row); + } + return rows; +} + +float OptionsMenu::RowY(const Row& row, int indexOnPage) const { + const float height = row.Page == VoicePage ? VoiceRowHeight : RowHeight; + return RowsTop + indexOnPage * height; +} + +float OptionsMenu::SliderScale() const { + return Page == VoicePage ? VoiceSliderScale : 1.0f; +} + +RectF OptionsMenu::SliderRect(float y) const { + const float scale = SliderScale(); + const float offsetY = Page == VoicePage ? VoiceSliderOffsetY : SliderOffset.y; + return RectF(ContentX + SliderOffset.x, y + offsetY, + SliderFrameSprite.ScaledWidth() * scale, + SliderFrameSprite.ScaledHeight() * scale); +} + +int OptionsMenu::GetChoice(const Row& row) const { + switch (row.Kind) { + case Setting::FullScreen: + return SDL_GetWindowFlags(Window->SDLWindow) & SDL_WINDOW_FULLSCREEN ? 0 + : 1; + case Setting::Resolution: { + int best = 0; + for (int i = 1; i < static_cast(Resolutions.size()); i++) { + if (std::abs(Resolutions[i].x - Window->WindowWidth) < + std::abs(Resolutions[best].x - Window->WindowWidth)) { + best = i; + } + } + return best; + } + case Setting::TriggerStopSkip: + return TriggerStopSkip ? 0 : 1; + case Setting::ShowTipsNotification: + return ShowTipsNotification ? 0 : 1; + case Setting::AutoQuickSave: { + const bool scene = AutoQuickSave & +AutoQuickSaveType::OnScene; + const bool trigger = AutoQuickSave & +AutoQuickSaveType::OnTrigger; + return scene && trigger ? 2 : scene ? 0 : trigger ? 1 : 3; + } + case Setting::SkipRead: + return SkipRead ? 0 : 1; + case Setting::SyncVoice: + return SyncVoice ? 0 : 1; + case Setting::SkipVoice: + return SkipVoice ? 0 : 1; + default: + return 0; + } +} + +void OptionsMenu::SetChoice(const Row& row, int choice) { + const bool on = choice == 0; + switch (row.Kind) { + case Setting::FullScreen: + SDL_SetWindowFullscreen(Window->SDLWindow, on); + break; + case Setting::Resolution: + if (!(SDL_GetWindowFlags(Window->SDLWindow) & SDL_WINDOW_FULLSCREEN)) { + SDL_SetWindowSize(Window->SDLWindow, Resolutions[choice].x, + Resolutions[choice].y); + } + break; + case Setting::TriggerStopSkip: + TriggerStopSkip = on; + break; + case Setting::ShowTipsNotification: + ShowTipsNotification = on; + break; + case Setting::AutoQuickSave: { + const std::array values = { + +AutoQuickSaveType::OnScene, +AutoQuickSaveType::OnTrigger, + +AutoQuickSaveType::OnScene | +AutoQuickSaveType::OnTrigger, 0}; + AutoQuickSave = values[choice]; + } break; + case Setting::SkipRead: + SkipRead = on; + break; + case Setting::SyncVoice: + SyncVoice = on; + break; + case Setting::SkipVoice: + SkipVoice = on; + break; + default: + break; + } +} + +float OptionsMenu::GetSlider(const Row& row) const { + switch (row.Kind) { + case Setting::TextSpeed: + return ToUnit(TextSpeed, TextSpeedBounds); + case Setting::AutoSpeed: + return ToUnit(AutoSpeed, AutoSpeedBounds); + case Setting::VoiceVolume: + return Audio::GroupVolumes[Audio::ACG_Voice]; + case Setting::MusicVolume: + return Audio::GroupVolumes[Audio::ACG_BGM]; + case Setting::SEVolume: + return Audio::GroupVolumes[Audio::ACG_SE]; + case Setting::MovieVolume: + return Audio::GroupVolumes[Audio::ACG_Movie]; + case Setting::CharacterVoice: + return VoiceVolume[row.Index]; + default: + return 0.0f; + } +} + +void OptionsMenu::SetSlider(const Row& row, float value) { + value = std::clamp(value, 0.0f, 1.0f); + switch (row.Kind) { + case Setting::TextSpeed: + TextSpeed = FromUnit(value, TextSpeedBounds); + break; + case Setting::AutoSpeed: + AutoSpeed = FromUnit(value, AutoSpeedBounds); + break; + case Setting::VoiceVolume: + Audio::GroupVolumes[Audio::ACG_Voice] = value; + break; + case Setting::MusicVolume: + Audio::GroupVolumes[Audio::ACG_BGM] = value; + break; + case Setting::SEVolume: + Audio::GroupVolumes[Audio::ACG_SE] = value; + break; + case Setting::MovieVolume: + Audio::GroupVolumes[Audio::ACG_Movie] = value; + break; + case Setting::CharacterVoice: + VoiceVolume[row.Index] = value; + break; + default: + break; + } +} + +bool OptionsMenu::IsMuted(const Row& row) const { + return row.Kind == Setting::CharacterVoice && VoiceMuted[row.Index]; +} + +void OptionsMenu::Show() { + if (State == Shown || State == Showing) return; + State = Showing; + Cursor = 0; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; +} + +void OptionsMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +void OptionsMenu::UpdateInput(float dt) { + if (!IsFocused || GetFlag(SF_OPTION_CLOSED)) return; + const std::vector rows = PageRows(Page); + const int count = static_cast(rows.size()); + const glm::vec2 mouse = Input::CurMousePos; + const bool click = Input::MouseButtonWentDown[SDL_BUTTON_LEFT]; + + // Pages: Q / E, or click a page button. + int nextPage = Page; + if (PADinputButtonWentDown & PAD1R1) nextPage = (Page + 1) % PageCount; + if (PADinputButtonWentDown & PAD1L1) + nextPage = (Page + PageCount - 1) % PageCount; + for (int page = 0; page < PageCount; page++) { + if (click && PageButtonBounds[page].ContainsPoint(mouse)) nextPage = page; + } + if (nextPage != Page) { + Page = nextPage; + Cursor = 0; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + return; + } + if (count == 0) return; + + int next = Cursor; + if (PADinputButtonRepeatAccelDown & PAD1UP) + next = (Cursor + count - 1) % count; + if (PADinputButtonRepeatAccelDown & PAD1DOWN) next = (Cursor + 1) % count; + for (int i = 0; i < count; i++) { + const float y = RowY(*rows[i], i); + const float height = Page == VoicePage ? VoiceRowHeight : RowHeight; + const RectF bounds(ContentX - RowPadding, y - RowPadding, RowWidth, height); + if (bounds.ContainsPoint(mouse) && Input::PrevMousePos != mouse) next = i; + } + if (next != Cursor) { + Cursor = next; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + + const Row& row = *rows[Cursor]; + const float y = RowY(row, Cursor); + const int dir = (PADinputButtonRepeatAccelDown & PAD1RIGHT) ? 1 + : (PADinputButtonRepeatAccelDown & PAD1LEFT) ? -1 + : 0; + if (!row.Choices.empty()) { + const int choices = static_cast(row.Choices.size()); + int choice = GetChoice(row); + int picked = std::clamp(choice + dir, 0, choices - 1); + if (PADinputButtonWentDown & PAD1A) picked = (choice + 1) % choices; + for (int i = 0; i < choices && click; i++) { + const Sprite& word = row.Choices[i]; + const RectF bounds(ContentX + word.Bounds.X - row.Label.Bounds.X, + y + word.Bounds.Y - row.Label.Bounds.Y, + word.ScaledWidth(), word.ScaledHeight()); + if (bounds.ContainsPoint(mouse)) picked = i; + } + if (picked != choice) { + SetChoice(row, picked); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); + } + } else { + float value = GetSlider(row) + dir * SliderStep; + const RectF slider = SliderRect(y); + if (Input::MouseButtonIsDown[SDL_BUTTON_LEFT] && + slider.ContainsPoint(mouse)) { + value = (mouse.x - slider.X - SliderFillOffset.x * SliderScale()) / + (SliderFillSprite.ScaledWidth() * SliderScale()); + } + if (std::abs(value - GetSlider(row)) > 1e-4f) { + SetSlider(row, value); + if (dir != 0) Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + if (row.Kind == Setting::CharacterVoice && + PADinputButtonWentDown & (PAD1A | PAD1Y)) { + VoiceMuted[row.Index] = !VoiceMuted[row.Index]; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); + } + } + + if (PADinputButtonWentDown & PAD1B || + Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]) { + SetFlag(SF_OPTION_CLOSED, true); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 3, false, 0); + } +} + +void OptionsMenu::Update(float dt) { + const int count = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo ? ScrWork[SW_SYSSUBMENUCT] : 0; + if (count > LastSubMenuCount && count > 0) { + Show(); + } else if (count < LastSubMenuCount) { + Hide(); + } + LastSubMenuCount = count; + + if (State == Showing && count >= CountMax) { + State = Shown; + IsFocused = UI::FocusedMenu == this; + } else if (State == Hiding && count <= 0) { + State = Hidden; + } + if (State == Shown) UpdateInput(dt); +} + +void OptionsMenu::Render() { + if (State == Hidden) return; + const float alpha = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo + ? std::clamp(ScrWork[SW_SYSSUBMENUCT] / float(CountMax), 0.0f, 1.0f) + : 0.0f; + const glm::vec4 tint(1.0f, 1.0f, 1.0f, alpha); + const glm::vec4 dim(glm::vec3(UnselectedBrightness), alpha); + + Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), tint); + Renderer->DrawSprite(PageButtonSprites[Page], PageButtonBounds[Page].GetPos(), + tint); + Renderer->DrawSprite(PageHeadingSprites[Page], HeadingPos, tint); + + const std::vector rows = PageRows(Page); + for (int i = 0; i < static_cast(rows.size()); i++) { + const Row& row = *rows[i]; + const float y = RowY(row, i); + if (i == Cursor && State == Shown) { + const float height = Page == VoicePage ? VoiceRowHeight : RowHeight; + Renderer->DrawQuad( + RectF(ContentX - RowPadding, y - RowPadding, RowWidth, height), + glm::vec4(HighlightColor, HighlightOpacity * alpha)); + } + Renderer->DrawSprite(row.Label, glm::vec2(ContentX, y), tint); + + if (!row.Choices.empty()) { + const int selected = GetChoice(row); + for (int c = 0; c < static_cast(row.Choices.size()); c++) { + const Sprite& word = row.Choices[c]; + Renderer->DrawSprite( + word, + glm::vec2(ContentX + word.Bounds.X - row.Label.Bounds.X, + y + word.Bounds.Y - row.Label.Bounds.Y), + c == selected ? tint : dim); + } + continue; + } + + const float scale = SliderScale(); + const float value = GetSlider(row); + const RectF frame = SliderRect(y); + const glm::vec2 fillPos = frame.GetPos() + SliderFillOffset * scale; + const glm::vec2 fillSize = glm::vec2(SliderFillSprite.ScaledWidth(), + SliderFillSprite.ScaledHeight()) * + scale; + Renderer->DrawSprite(SliderFrameSprite, frame, tint); + Sprite fill = SliderFillSprite; + fill.Bounds.Width *= value; + Renderer->DrawSprite( + fill, RectF(fillPos.x, fillPos.y, fillSize.x * value, fillSize.y), + tint); + const glm::vec2 knobSize = glm::vec2(SliderKnobSprite.ScaledWidth(), + SliderKnobSprite.ScaledHeight()) * + scale; + Renderer->DrawSprite( + SliderKnobSprite, + RectF(fillPos.x + fillSize.x * value - knobSize.x / 2.0f, + frame.Y + SliderKnobOffsetY * scale, knobSize.x, knobSize.y), + tint); + if (IsMuted(row)) { + Renderer->DrawSprite(VoiceOffSprite, + RectF(fillPos.x, fillPos.y, fillSize.x, fillSize.y), + tint); + } + } + + Renderer->DrawSprite(Page == VoicePage ? VoiceGuideSprite : GuideSprite, + Page == VoicePage ? VoiceGuidePos : GuidePos, tint); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/optionsmenu.h b/src/games/sg0/optionsmenu.h new file mode 100644 index 000000000..1b918e746 --- /dev/null +++ b/src/games/sg0/optionsmenu.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "../../ui/optionsmenu.h" +#include "../../profile/games/sg0/optionsmenu.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 config (config.dds): BASIC / TEXT / SOUND / VOICE pages of +// rows, each a label with either choice words or a MIN-MAX slider, bound to +// Impacto's config values. _Startup_win opens it with SW_SYSSUBMENUNO 5 and +// SW_SYSSUBMENUCT counting 0..32, runs Option(1) every frame until flag 1830 +// (closed) is set, then saves; Delete asks to restore the defaults (Option(4)). +class OptionsMenu : public UI::OptionsMenu { + public: + OptionsMenu(); + + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + + protected: + void UpdateVisibility() override {} + + private: + using Row = Profile::SG0::OptionsMenu::Row; + + std::vector PageRows(int page) const; + float RowY(const Row& row, int indexOnPage) const; + float SliderScale() const; + RectF SliderRect(float y) const; + + int GetChoice(const Row& row) const; + void SetChoice(const Row& row, int choice); + float GetSlider(const Row& row) const; + void SetSlider(const Row& row, float value); + bool IsMuted(const Row& row) const; + + int Page = 0; + int Cursor = 0; + int LastSubMenuCount = 0; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/phone.cpp b/src/games/sg0/phone.cpp new file mode 100644 index 000000000..cb2c7b347 --- /dev/null +++ b/src/games/sg0/phone.cpp @@ -0,0 +1,703 @@ +#include "phone.h" +#include "rine.h" +#include "fullwidth.h" + +#include + +#include "../../profile/games/sg0/phone.h" +#include "../../profile/dialogue.h" +#include "../../profile/vm.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/vm.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::Phone; +using namespace Impacto::Vm::Interface; + +// Phone flags and work indices, from the SG0 scripts (sg0-phone.md section 4). +// They overlap CHAOS;CHILD's SW_BGEFF1_* names, so they stay local here. +namespace { +constexpr int SF_PHONE_MAY_OPEN = 1611; // engine: no other UI is up +constexpr int SF_PHONE_SHOWN = 2600; // phone raised (script or player) +constexpr int SF_PHONE_SESSION_ACTIVE = 2605; // script: player session running +constexpr int SF_PHONE_CLOSED = 2603; // engine: the player closed it +constexpr int SF_PHONE_SESSION_END = 2604; // engine: interactive session over +constexpr int SF_AMADEUS_LOCKED = 2613; +constexpr int SF_TRIGGER_FIRED = 2620; // engine: armed action performed +constexpr int SF_CALL_RINGING = 2740; +constexpr int SF_AMADEUS_RINGING = 2743; +constexpr int SF_REJECT_CALL_ARMED = 2744; +constexpr int SF_REJECT_AMADEUS_ARMED = 2745; +constexpr int SF_RINE_ARMED = 2748; +constexpr int SF_POWER_OFF_ARMED = 2749; +constexpr int SF_AMADEUS_CALL_ARMED = 2750; + +constexpr int SW_PHONE_SLIDE = 6601; // 0 (lowered) .. 29 (raised) +constexpr int SW_PHONE_MODE = 6602; // current screen +constexpr int SW_PHONE_OPEN_MODE = 6604; // screen the player opens to +constexpr int SW_PHONE_TRIGGER_KIND = 6608; // 1 call, 3 RINE +constexpr int SW_PHONE_TRIGGER_RESULT = 6609; // 0 answer / 1 reject / slot +constexpr int SW_PHONE_CALLER = 6613; // contact slot of the caller +constexpr int SlideMax = 29; + +// _phone UI string ids (label 2). +constexpr uint16_t StrOK = 5, StrCancel = 6, StrPowerOff = 7, StrContacts = 10, + StrRine = 11, StrAmadeus = 12, StrIncomingCall = 17, + StrAnswer = 18, StrReject = 19, StrCalling = 21, StrEnd = 22, + StrCallFromKurisu = 25, StrConnectToKurisu = 27; +constexpr int OwnNumberSlot = 10; +} // namespace + +void Phone::LoadTables(uint32_t scriptBufferId, + std::span labels) { + ScriptBuffer = scriptBufferId; + const std::span script = Vm::ScriptBuffers[scriptBufferId]; + + // labels[0]: u16 UI string ids up to the next table. + const uint32_t stringsStart = + Vm::ScriptGetLabelAddress(scriptBufferId, labels[0]); + const uint32_t stringsEnd = + Vm::ScriptGetLabelAddress(scriptBufferId, labels[1]); + UiStrings.clear(); + for (uint32_t pos = stringsStart; pos + 2 <= stringsEnd; pos += 2) { + UiStrings.push_back(UnalignedRead(&script[pos])); + } + + // labels[1]: u16 keypad string, then {u8 chara, u16 name, u16 number, + // u16 registered flag, u16 call-trigger flag} until chara 0xFF. + Contacts.clear(); + uint32_t pos = Vm::ScriptGetLabelAddress(scriptBufferId, labels[1]) + 2; + while (pos + 9 <= script.size() && script[pos] != 0xFF) { + Contacts.push_back({UnalignedRead(&script[pos + 1]), + UnalignedRead(&script[pos + 5]), + UnalignedRead(&script[pos + 7])}); + pos += 9; + } + Loaded = true; +} + +std::vector Phone::Layout(uint16_t strId, glm::vec2 pos, + float fontSize) const { + return Layout(strId, pos, fontSize, {TextColor, OutlineColor}); +} + +std::vector Phone::Layout(uint16_t strId, glm::vec2 pos, + float fontSize, + DialogueColorPair colors) const { + Vm::Sc3VmThread dummy; + dummy.ScriptBufferId = ScriptBuffer; + dummy.IpOffset = Vm::ScriptGetStrAddress(ScriptBuffer, strId); + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + return TextLayoutPlainLine(&dummy, 255, *Profile::Dialogue::DialogueFont, + fontSize, colors, 1.0f, pos, TextAlignment::Left); +} + +void Phone::AddItem(uint16_t uiStr, Action act, int param) { + const uint16_t strId = uiStr < UiStrings.size() ? UiStrings[uiStr] : 0; + AddItemText(strId, act, param); +} + +void Phone::AddItemText(uint16_t strId, Action act, int param) { + const float y = ItemsTop + Items.size() * RowHeight; + Items.push_back( + {Layout(strId, glm::vec2(Padding.x, y), ItemFontSize), act, param}); +} + +Phone::Screen Phone::DisplayedScreen() const { + if (GetFlag(SF_CALL_RINGING) || GetFlag(SF_AMADEUS_RINGING)) { + return Screen::Call; + } + switch (ScrWork[SW_PHONE_MODE]) { + case 0: + return Screen::Off; + case 2: + return Screen::PowerOff; + case 7: + return Screen::Rine; + case 11: + return Screen::Contacts; + case 12: + case 13: + case 14: + case 15: + case 16: + return Screen::Call; + case 17: + case 18: + case 19: + return Screen::Amadeus; + default: + return Screen::Home; + } +} + +void Phone::Open(Screen screen) { + Current = screen; + CurrentMode = ScrWork[SW_PHONE_MODE]; + Title.clear(); + Subtitle.clear(); + Items.clear(); + Cursor = 0; + if (!Loaded) return; + + const auto ui = [this](uint16_t id) { + return id < UiStrings.size() ? UiStrings[id] : uint16_t{0}; + }; + const glm::vec2 titlePos = Padding; + const glm::vec2 subtitlePos = Padding + glm::vec2(0.0f, RowHeight); + + switch (screen) { + case Screen::Off: + break; + case Screen::Home: + AddItem(StrContacts, Action::Contacts); + AddItem(StrRine, Action::Rine); + AddItem(StrAmadeus, Action::Amadeus); + AddItem(StrPowerOff, Action::AskPowerOff); + break; + case Screen::PowerOff: + Title = Layout(ui(StrPowerOff), titlePos, TitleFontSize); + AddItem(StrOK, Action::PowerOff); + AddItem(StrCancel, Action::Back); + break; + case Screen::Contacts: + Title = Layout(ui(StrContacts), titlePos, TitleFontSize); + for (int slot = 0; slot < static_cast(Contacts.size()); slot++) { + if (slot == OwnNumberSlot || !GetFlag(Contacts[slot].RegisteredFlag)) { + continue; + } + AddItemText(Contacts[slot].NameStr, Action::CallContact, slot); + } + break; + case Screen::Rine: + RineLaidOut = UINT64_MAX; // relaid out on the next update + RineReplyCursor = 0; + break; + case Screen::Amadeus: + Title = Layout(ui(StrAmadeus), titlePos, TitleFontSize); + Subtitle = Layout(ui(StrConnectToKurisu), subtitlePos, ItemFontSize); + AddItem(StrOK, Action::Connect); + AddItem(StrCancel, Action::Back); + break; + case Screen::Call: { + const int caller = ScrWork[SW_PHONE_CALLER]; + if (GetFlag(SF_AMADEUS_RINGING)) { + Title = Layout(ui(StrCallFromKurisu), titlePos, TitleFontSize); + } else if (caller >= 0 && caller < static_cast(Contacts.size())) { + Title = Layout(Contacts[caller].NameStr, titlePos, TitleFontSize); + } + const bool ringing = + GetFlag(SF_CALL_RINGING) || GetFlag(SF_AMADEUS_RINGING); + const int mode = ScrWork[SW_PHONE_MODE]; + const uint16_t status = ringing || mode == 13 ? StrIncomingCall + : mode == 16 ? StrCalling + : mode == 15 ? StrEnd + : 0xFFFF; + if (status != 0xFFFF) { + Subtitle = Layout(ui(status), subtitlePos, ItemFontSize); + } + if (ringing) { + AddItem(StrAnswer, Action::Answer); + AddItem(StrReject, Action::Reject); + if (GetFlag(SF_POWER_OFF_ARMED)) + AddItem(StrPowerOff, Action::AskPowerOff); + } + } break; + } +} + +void Phone::Fire(int result, int kind) { + ScrWork[SW_PHONE_TRIGGER_RESULT] = result; + ScrWork[SW_PHONE_TRIGGER_KIND] = kind; + SetFlag(SF_TRIGGER_FIRED, true); + // End the session without SF_PHONE_CLOSED: the phone stays raised for the + // scripted follow-up. + SetFlag(SF_PHONE_SESSION_END, true); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); +} + +void Phone::Close() { + SetFlag(SF_PHONE_CLOSED, true); + SetFlag(SF_PHONE_SESSION_END, true); +} + +void Phone::Back() { + if (Current == Screen::Home || Current == Screen::Call || + Current == Screen::Off) { + Close(); + } else if (GetFlag(SF_CALL_RINGING) || GetFlag(SF_AMADEUS_RINGING)) { + Open(Screen::Call); + } else { + Open(Screen::Home); + } +} + +void Phone::Activate(const Item& item) { + const bool callRinging = GetFlag(SF_CALL_RINGING); + const bool amadeusRinging = GetFlag(SF_AMADEUS_RINGING); + switch (item.Act) { + case Action::Answer: + if (callRinging || amadeusRinging) + Fire(0, ScrWork[SW_PHONE_TRIGGER_KIND]); + break; + case Action::Reject: + // Reject only counts when the script armed it; otherwise it is the same + // as letting the phone ring. + if ((callRinging && GetFlag(SF_REJECT_CALL_ARMED)) || + (amadeusRinging && GetFlag(SF_REJECT_AMADEUS_ARMED))) { + Fire(1, ScrWork[SW_PHONE_TRIGGER_KIND]); + } else { + Close(); + } + break; + case Action::AskPowerOff: + Open(Screen::PowerOff); + break; + case Action::PowerOff: + if (GetFlag(SF_POWER_OFF_ARMED) || ScrWork[SW_PHONE_OPEN_MODE] == 2) { + Fire(ScrWork[SW_PHONE_TRIGGER_RESULT], ScrWork[SW_PHONE_TRIGGER_KIND]); + } else { + Close(); + } + break; + case Action::Back: + Back(); + break; + case Action::Contacts: + Open(Screen::Contacts); + break; + case Action::Rine: + if (GetFlag(SF_RINE_ARMED)) { + Fire(ScrWork[SW_PHONE_TRIGGER_RESULT], 3); + } else { + Open(Screen::Rine); + } + break; + case Action::Amadeus: + Open(Screen::Amadeus); + break; + case Action::CallContact: + if (GetFlag(Contacts[item.Param].CallTriggerFlag)) { + Fire(item.Param, 1); + } else { + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 4, false, 0); + } + break; + case Action::Connect: + if (GetFlag(SF_AMADEUS_CALL_ARMED) && !GetFlag(SF_AMADEUS_LOCKED)) { + Fire(ScrWork[SW_PHONE_TRIGGER_RESULT], 2); + } else { + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 4, false, 0); + } + break; + } +} + +void Phone::BeginSession() { + // Startup and new game also run sub-type 1 as a reset; only the _system + // phone thread's call (session flag 2605 set) opens the phone for the player. + if (!GetFlag(SF_PHONE_SESSION_ACTIVE)) { + SessionOpen = false; + Hide(); + return; + } + SessionOpen = true; + Open(DisplayedScreen()); + Show(); +} + +void Phone::EndSession() { + SessionOpen = false; + Hide(); +} + +void Phone::Show() { + if (State == Shown) return; + State = Shown; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; + IsFocused = true; +} + +void Phone::Hide() { + if (State == Hidden) return; + State = Hidden; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +void Phone::TakeInput(float dt) { + if (!SessionOpen || !IsFocused) return; + UpdateInput(dt); + PADinputButtonWentDown = 0; + PADinputButtonRepeatDown = 0; + PADinputButtonRepeatAccelDown = 0; + PADinputMouseWentDown = 0; +} + +void Phone::UpdateInput(float dt) { + if (GetFlag(SF_PHONE_SESSION_END)) return; + if (Current == Screen::Rine) { + UpdateRineInput(); + return; + } + const int count = static_cast(Items.size()); + if (count > 0) { + int next = Cursor; + if (PADinputButtonWentDown & PAD1UP) next = (Cursor + count - 1) % count; + if (PADinputButtonWentDown & PAD1DOWN) next = (Cursor + 1) % count; + const glm::vec2 screen = BodyPos() + ScreenOffset; + for (int i = 0; i < count; i++) { + const RectF row(screen.x, + screen.y + ItemsTop + i * RowHeight - RowPadding, + ScreenSize.x, RowHeight); + if (!row.ContainsPoint(Input::CurMousePos)) continue; + if (Input::PrevMousePos != Input::CurMousePos) next = i; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + Cursor = i; + Activate(Items[i]); + return; + } + } + if (next != Cursor) { + Cursor = next; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + if (PADinputButtonWentDown & PAD1A) { + Activate(Items[Cursor]); + return; + } + } + if (PADinputButtonWentDown & (PAD1B | PAD1L2 | PAD1R2) || + Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]) { + Back(); + } +} + +void Phone::Update(float dt) { + // Slide counter: one step per 60 Hz frame toward raised or lowered. + SlideTimer += dt; + while (SlideTimer >= 1.0f / 60.0f) { + SlideTimer -= 1.0f / 60.0f; + int& slide = ScrWork[SW_PHONE_SLIDE]; + if (GetFlag(SF_PHONE_SHOWN)) { + if (slide < SlideMax) slide++; + } else if (slide > 0) { + slide--; + } + } + + SetFlag(SF_PHONE_MAY_OPEN, + UI::FocusedMenu == nullptr || UI::FocusedMenu == this); + + // Follow the scripts' screen changes while the phone is up. + if (ScrWork[SW_PHONE_SLIDE] > 0 && !SessionOpen) { + const Screen shown = DisplayedScreen(); + if (shown != Current || ScrWork[SW_PHONE_MODE] != CurrentMode) Open(shown); + } + + if (Current == Screen::Rine) { + if (RineLaidOut != RineState.Version) LayoutRine(); + if (SessionOpen) RineState.MarkRead(); + } +} + +glm::vec2 Phone::BodyPos() const { + const float t = ScrWork[SW_PHONE_SLIDE] / static_cast(SlideMax); + return glm::vec2(HiddenX + (ShownPos.x - HiddenX) * t, ShownPos.y); +} + +void Phone::Render() { + if (ScrWork[SW_PHONE_SLIDE] <= 0 && !GetFlag(SF_PHONE_SHOWN)) return; + + const glm::vec2 body = BodyPos(); + const RectF bodyDest(body.x, body.y, BodySprite.ScaledWidth() * Scale, + BodySprite.ScaledHeight() * Scale); + Renderer->DrawSprite(BodySprite, bodyDest); + + const glm::vec2 screen = body + ScreenOffset; + const RectF screenRect(screen.x, screen.y, ScreenSize.x, ScreenSize.y); + if (Current == Screen::Off) { + Renderer->DrawQuad(screenRect, glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + return; + } + Renderer->DrawSprite(WallpaperSprite, screenRect); + Renderer->DrawQuad(screenRect, glm::vec4(0.0f, 0.0f, 0.0f, ScreenShadeAlpha)); + if (Current == Screen::Rine) { + RenderRine(screen); + return; + } + + Font& font = *Profile::Dialogue::DialogueFont; + font.DrawProcessedText(Title, 1.0f, 1.0f, RendererOutlineMode::Full, screen); + font.DrawProcessedText(Subtitle, 1.0f, 1.0f, RendererOutlineMode::Full, + screen); + for (int i = 0; i < static_cast(Items.size()); i++) { + if (SessionOpen && i == Cursor) { + const RectF bar(screen.x, + screen.y + ItemsTop + i * RowHeight - RowPadding, + ScreenSize.x, RowHeight); + Renderer->DrawSprite(SelectionSprite, bar); + } + font.DrawProcessedText(Items[i].Text, 1.0f, 1.0f, RendererOutlineMode::Full, + screen); + } +} + +// --- RINE screen ---------------------------------------------------------- + +namespace { +glm::vec4 Rgba(uint32_t rgb, float alpha = 1.0f) { + return glm::vec4(((rgb >> 16) & 0xFF) / 255.0f, ((rgb >> 8) & 0xFF) / 255.0f, + (rgb & 0xFF) / 255.0f, alpha); +} + +bool IsSpaceGlyph(const ProcessedTextGlyph& glyph) { + return glyph.CharId == 0 || glyph.CharId == 63; // half- and full-width space +} + +// Breaks a single laid-out line at spaces so it fits maxWidth; returns the +// size of the wrapped block. Glyphs start at x 0. +glm::vec2 WrapGlyphs(std::vector& glyphs, float maxWidth, + float lineHeight) { + if (glyphs.empty()) return glm::vec2(0.0f); + size_t lineStart = 0; + int lines = 1; + float width = 0.0f; + for (size_t i = 0; i < glyphs.size(); i++) { + if (glyphs[i].DestRect.X + glyphs[i].DestRect.Width > maxWidth && + i > lineStart) { + size_t next = i; + for (size_t j = i; j > lineStart; j--) { + if (IsSpaceGlyph(glyphs[j])) { + next = j + 1; + break; + } + } + if (next >= glyphs.size()) break; + const float shift = glyphs[next].DestRect.X; + for (size_t k = next; k < glyphs.size(); k++) { + glyphs[k].Move(glm::vec2(-shift, lineHeight)); + } + lineStart = next; + i = next; + lines++; + } + width = std::max(width, glyphs[i].DestRect.X + glyphs[i].DestRect.Width); + } + return glm::vec2(width, lines * lineHeight); +} +} // namespace + +Sprite Phone::AvatarFor(int avatar) const { + Sprite sprite = RineAvatarSprite; + sprite.Bounds.Y += avatar * RineAvatarStepY; + return sprite; +} + +Sprite Phone::StickerFor(int sticker) const { + Sprite sprite = RineStickerSprite; + sprite.Bounds.X += (sticker % RineStickerColumns) * RineStickerStep.x; + sprite.Bounds.Y += (sticker / RineStickerColumns) * RineStickerStep.y; + return sprite; +} + +void Phone::LayoutRine() { + RineLaidOut = RineState.Version; + const DialogueColorPair bubbleText{RineTextColor, RineTextColor}; + + RineBubbles.clear(); + float y = RineGap; + for (const RineMessage& message : RineState.Messages) { + RineBubble bubble; + const bool own = message.Sender == RineState.Owner; + glm::vec2 size; + if (message.Kind == 1) { + bubble.Sticker = message.Value; + size = RineStickerSize; + } else { + bubble.Text = Layout(static_cast(message.Value), + glm::vec2(0.0f), RineFontSize, bubbleText); + size = WrapGlyphs(bubble.Text, RineBubbleMaxWidth, RineLineHeight) + + 2.0f * RineBubblePadding; + } + const float x = own ? ScreenSize.x - RineMargin - size.x + : RineMargin * 2.0f + RineAvatarSize; + if (!own && message.Sender >= 0 && + message.Sender < static_cast(RineState.Participants.size())) { + bubble.Avatar = RineState.Participants[message.Sender].Avatar; + } + bubble.Box = RectF(x, y, size.x, size.y); + for (ProcessedTextGlyph& glyph : bubble.Text) { + glyph.Move(glm::vec2(x, y) + RineBubblePadding); + } + y += std::max(size.y, own ? 0.0f : RineAvatarSize) + RineGap; + RineBubbles.push_back(std::move(bubble)); + } + RineContentHeight = y; + + // Title: the first member other than the thread owner. + uint16_t titleStr = StrRine < UiStrings.size() ? UiStrings[StrRine] : 0; + for (int member : RineState.Members) { + if (member != RineState.Owner && member >= 0 && + member < static_cast(RineState.Participants.size())) { + titleStr = RineState.Participants[member].NameStr; + break; + } + } + RineTitle = Layout(titleStr, Padding, TitleFontSize); + + RineInput.clear(); + if (RineState.InputText >= 0) { + RineInput = Layout(static_cast(RineState.InputText), + glm::vec2(RineMargin, 0.0f), RineFontSize, bubbleText); + SquishText(RineInput, ScreenSize.x - 2.0f * RineMargin, RineMargin); + } + + RineReplies.clear(); + int sender = 0; + std::vector options; + if (RineState.ReadReplyMenu(sender, RineReplyKind, options)) { + for (size_t i = 0; i < options.size(); i++) { + RineReply reply; + if (RineReplyKind == 1) { + reply.Sticker = options[i].Value; + } else { + reply.Text = + Layout(static_cast(options[i].Value), + glm::vec2(RineMargin, i * RineReplyRowHeight), RineFontSize); + SquishText(reply.Text, ScreenSize.x - 2.0f * RineMargin, RineMargin); + } + RineReplies.push_back(std::move(reply)); + } + } + RineReplyCursor = + std::clamp(RineReplyCursor, 0, + std::max(0, static_cast(RineReplies.size()) - 1)); +} + +float Phone::RineReplyAreaHeight() const { + if (SessionOpen && !RineReplies.empty()) { + return RineReplyKind == 1 + ? RineReplyStickerSize.y + 2.0f * RineGap + : RineReplies.size() * RineReplyRowHeight + 2.0f * RineGap; + } + return RineInput.empty() ? 0.0f : RineInputHeight; +} + +void Phone::UpdateRineInput() { + const int count = static_cast(RineReplies.size()); + if (count > 0 && RineState.MenuPending) { + int next = RineReplyCursor; + if (PADinputButtonWentDown & (PAD1UP | PAD1LEFT)) { + next = (RineReplyCursor + count - 1) % count; + } + if (PADinputButtonWentDown & (PAD1DOWN | PAD1RIGHT)) { + next = (RineReplyCursor + 1) % count; + } + if (next != RineReplyCursor) { + RineReplyCursor = next; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + if (PADinputButtonWentDown & PAD1A) { + RineState.Reply(RineReplyCursor); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); + return; + } + } + if (PADinputButtonWentDown & (PAD1B | PAD1L2 | PAD1R2) || + Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]) { + Back(); + } +} + +void Phone::RenderRine(glm::vec2 screen) { + Fonts::Font& font = *Profile::Dialogue::DialogueFont; + font.DrawProcessedText(RineTitle, 1.0f, 1.0f, RendererOutlineMode::Full, + screen); + + const float reserved = RineReplyAreaHeight(); + const float areaHeight = ScreenSize.y - RineTop - reserved; + const float scroll = std::max(0.0f, RineContentHeight - areaHeight); + const glm::vec2 origin(screen.x, screen.y + RineTop - scroll); + + Renderer->EnableScissor(); + Renderer->SetScissorRect( + RectF(screen.x, screen.y + RineTop, ScreenSize.x, areaHeight)); + for (size_t i = 0; i < RineBubbles.size(); i++) { + const RineBubble& bubble = RineBubbles[i]; + const RectF box = bubble.Box + origin; + if (bubble.Avatar >= 0) { + Renderer->DrawSprite( + AvatarFor(bubble.Avatar), + RectF(origin.x + RineMargin, box.Y, RineAvatarSize, RineAvatarSize)); + } + if (bubble.Sticker >= 0) { + Renderer->DrawSprite(StickerFor(bubble.Sticker), box); + continue; + } + const bool own = RineState.Messages[i].Sender == RineState.Owner; + Renderer->DrawQuad(box, Rgba(own ? RineOwnColor : RineOtherColor)); + DrawTextAt(bubble.Text, origin, 1.0f, RendererOutlineMode::None); + } + Renderer->DisableScissor(); + + if (reserved <= 0.0f) return; + const float top = screen.y + ScreenSize.y - reserved; + Renderer->DrawQuad(RectF(screen.x, top, ScreenSize.x, reserved), + glm::vec4(0.0f, 0.0f, 0.0f, 0.55f)); + + if (SessionOpen && !RineReplies.empty()) { + for (int i = 0; i < static_cast(RineReplies.size()); i++) { + const RineReply& reply = RineReplies[i]; + if (RineReplyKind == 1) { + const float x = + screen.x + RineMargin + i * (RineReplyStickerSize.x + RineMargin); + const RectF box(x, top + RineGap, RineReplyStickerSize.x, + RineReplyStickerSize.y); + if (i == RineReplyCursor) { + Renderer->DrawQuad(box, glm::vec4(1.0f, 0.65f, 0.25f, 0.45f)); + } + Renderer->DrawSprite(StickerFor(reply.Sticker), box); + continue; + } + const float rowY = top + RineGap + i * RineReplyRowHeight; + if (i == RineReplyCursor) { + Renderer->DrawSprite(SelectionSprite, + RectF(screen.x, rowY - RowPadding / 2.0f, + ScreenSize.x, RineReplyRowHeight)); + } + font.DrawProcessedText(reply.Text, 1.0f, 1.0f, RendererOutlineMode::Full, + glm::vec2(screen.x, top + RineGap)); + } + return; + } + + Renderer->DrawQuad(RectF(screen.x + RineMargin / 2.0f, top + 4.0f, + ScreenSize.x - RineMargin, reserved - 8.0f), + Rgba(RineOtherColor)); + font.DrawProcessedText( + RineInput, 1.0f, 1.0f, RendererOutlineMode::None, + glm::vec2(screen.x, top + (reserved - RineFontSize) / 2.0f)); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/phone.h b/src/games/sg0/phone.h new file mode 100644 index 000000000..d3f95de56 --- /dev/null +++ b/src/games/sg0/phone.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include + +#include "../../ui/menu.h" +#include "../../text/text.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 phone (opcode 10:37, PhoneSG0). The phone is how the player +// makes route decisions: scripts arm a trigger (incoming call, power off, +// calling a contact, opening RINE, Amadeus) and poll flag 2620 after every +// message line; the engine sets it, plus the result in ScrWork 6609 / 6608, +// when the player performs the armed action. Scripts also wait for the slide +// counter ScrWork 6601 to reach 29 (raised) or 0 (lowered), which the engine +// animates. +// +// Session handling, the triggers, a plain overlay with text menus, and the +// RINE screen (see rine.h for the message model). +class Phone : public Menu { + public: + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + + // PhoneSG0 sub-types. + void LoadTables(uint32_t scriptBufferId, std::span labels); + void BeginSession(); + void EndSession(); + + // Runs before the VM each frame: while the player has the phone open, the + // phone takes the confirm / cancel / direction input so the message window + // does not advance underneath it. + void TakeInput(float dt); + + private: + enum class Screen { Off, Home, PowerOff, Contacts, Rine, Amadeus, Call }; + enum class Action { + Answer, + Reject, + AskPowerOff, + PowerOff, + Back, + Contacts, + Rine, + Amadeus, + CallContact, + Connect + }; + struct Contact { + uint16_t NameStr; + uint16_t RegisteredFlag; + uint16_t CallTriggerFlag; + }; + struct Item { + std::vector Text; + Action Act; + int Param; + }; + + Screen DisplayedScreen() const; + void Open(Screen screen); + void Activate(const Item& item); + void Back(); + void Fire(int result, int kind); + void Close(); + std::vector Layout(uint16_t strId, glm::vec2 pos, + float fontSize) const; + std::vector Layout(uint16_t strId, glm::vec2 pos, + float fontSize, + DialogueColorPair colors) const; + void AddItem(uint16_t uiStr, Action act, int param = 0); + void AddItemText(uint16_t strId, Action act, int param = 0); + glm::vec2 BodyPos() const; + + // RINE screen. + struct RineBubble { + RectF Box; // relative to the top of the message area + std::vector Text; + int Sticker = -1; + int Avatar = -1; + }; + struct RineReply { + std::vector Text; + int Sticker = -1; + }; + void LayoutRine(); + void RenderRine(glm::vec2 screen); + void UpdateRineInput(); + float RineReplyAreaHeight() const; + Sprite AvatarFor(int avatar) const; + Sprite StickerFor(int sticker) const; + + uint32_t ScriptBuffer = 0; + bool Loaded = false; + std::vector UiStrings; + std::vector Contacts; + + bool SessionOpen = false; + Screen Current = Screen::Home; + int CurrentMode = -1; + std::vector Title; + std::vector Subtitle; + std::vector Items; + int Cursor = 0; + float SlideTimer = 0.0f; + + uint64_t RineLaidOut = UINT64_MAX; + std::vector RineBubbles; + float RineContentHeight = 0.0f; + std::vector RineTitle; + std::vector RineInput; + std::vector RineReplies; + int RineReplyKind = 0; + int RineReplyCursor = 0; +}; + +inline Phone* PhonePtr = nullptr; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/rine.cpp b/src/games/sg0/rine.cpp new file mode 100644 index 000000000..d58f6da16 --- /dev/null +++ b/src/games/sg0/rine.cpp @@ -0,0 +1,165 @@ +#include "rine.h" + +#include + +#include "../../mem.h" +#include "../../vm/vm.h" +#include "../../vm/expression.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +namespace { +constexpr int SW_RINE_TEXT_BASE = 6655; +constexpr int SW_RINE_SCROLL_MAX = 6653; +constexpr int SF_RINE_NOT_REPLIED = 2614; +constexpr int SF_RINE_READ = 2615; +constexpr int SF_RINE_NOTIFY_A = 2741; +constexpr int SF_RINE_NOTIFY_B = 2742; +constexpr int BranchThreadGroup = 8; // the phone's thread group +constexpr int TypingDelayFrames = 40; +constexpr int MemberListEnd = 15; + +bool IsPhoneOp(const uint8_t* ip, uint8_t subType) { + return ip[0] == 0x10 && ip[1] == 0x37 && ip[2] == subType; +} +} // namespace + +void Rine::LoadParticipants(uint32_t phoneBuffer, uint16_t label) { + PhoneBuffer = phoneBuffer; + const std::span script = Vm::ScriptBuffers[phoneBuffer]; + uint32_t pos = Vm::ScriptGetLabelAddress(phoneBuffer, label); + Participants.clear(); + for (int i = 0; i <= MemberListEnd && pos + 3 <= script.size(); + i++, pos += 3) { + Participants.push_back( + {script[pos], UnalignedRead(&script[pos + 1])}); + } +} + +void Rine::SelectTextList(uint16_t label) { + const uint32_t pos = Vm::ScriptGetLabelAddress(PhoneBuffer, label); + ScrWork[SW_RINE_TEXT_BASE] = + UnalignedRead(&Vm::ScriptBuffers[PhoneBuffer][pos]); +} + +void Rine::NewThread(int owner) { + Owner = owner; + Members = {owner}; + Messages.clear(); + InputText = -1; + Version++; +} + +void Rine::AddMember(int participant) { + if (participant == MemberListEnd) return; + if (std::find(Members.begin(), Members.end(), participant) == Members.end()) { + Members.push_back(participant); + Version++; + } +} + +void Rine::AddMessage(int sender, int kind, int value) { + Messages.push_back({sender, kind, value}); + ScrWork[SW_RINE_SCROLL_MAX] = 0; // the phone UI scrolls to the newest itself + Version++; +} + +void Rine::ClearAll() { + Members.clear(); + Messages.clear(); + InputText = -1; + MenuPending = false; + Version++; +} + +void Rine::SetInput(int textValue) { + InputText = textValue; + Version++; +} + +void Rine::ClearInput() { + InputText = -1; + Version++; +} + +void Rine::SetReplyMenu(uint32_t scriptBufferId, uint16_t label) { + MenuBuffer = scriptBufferId; + MenuLabel = label; + MenuPending = true; + Version++; +} + +void Rine::CancelReplyMenu() { + MenuPending = false; + Version++; +} + +bool Rine::ReadReplyMenu(int& sender, int& kind, + std::vector& options) const { + if (!MenuPending) return false; + Vm::Sc3VmThread reader; + reader.ScriptBufferId = MenuBuffer; + reader.IpOffset = Vm::ScriptGetLabelAddress(MenuBuffer, MenuLabel); + if (!IsPhoneOp(reader.GetIp(), 0x0C)) return false; + reader.IpOffset += 3; + sender = Vm::ExpressionEval(&reader); + kind = Vm::ExpressionEval(&reader); + + options.clear(); + while (IsPhoneOp(reader.GetIp(), 0x0D) && options.size() < 8) { + reader.IpOffset += 3; + const int value = Vm::ExpressionEval(&reader); + const uint16_t branch = UnalignedRead(reader.GetIp()); + reader.IpOffset += 2; + options.push_back({value, branch}); + } + return !options.empty(); +} + +void Rine::Reply(int optionIndex) { + int sender = 0; + int kind = 0; + std::vector options; + if (!ReadReplyMenu(sender, kind, options) || optionIndex < 0 || + optionIndex >= static_cast(options.size())) { + return; + } + const RineReplyOption& option = options[optionIndex]; + AddMessage(sender, kind, option.Value); + SetFlag(SF_RINE_NOT_REPLIED, false); + MenuPending = false; + + Vm::Sc3VmThread* branch = Vm::CreateThread(BranchThreadGroup); + branch->GroupId = BranchThreadGroup; + branch->ScriptBufferId = MenuBuffer; + branch->IpOffset = Vm::ScriptGetLabelAddress(MenuBuffer, option.BranchLabel); + BranchThreadId = branch->Id; + TypingFrames = 0; +} + +bool Rine::IsBranchThread(const Vm::Sc3VmThread* thread) const { + return thread->Id == BranchThreadId; +} + +void Rine::EndBranch(Vm::Sc3VmThread* thread) { + thread->Flags = Vm::TF_Destroy; + BranchThreadId = UINT32_MAX; +} + +bool Rine::TypingDone() { + if (++TypingFrames < TypingDelayFrames) return false; + TypingFrames = 0; + return true; +} + +void Rine::MarkRead() { + SetFlag(SF_RINE_READ, true); + SetFlag(SF_RINE_NOTIFY_A, false); + SetFlag(SF_RINE_NOTIFY_B, false); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/rine.h b/src/games/sg0/rine.h new file mode 100644 index 000000000..1f147e315 --- /dev/null +++ b/src/games/sg0/rine.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +#include "../../vm/thread.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 RINE (the in-game messenger), driven by PhoneSG0 sub-types: +// 0x2A new thread, 0x2B/0x2C members, 0x2E messages, 0x30/0x2F input text, +// 0x32 text list, 0x0A reply menu, 0x0F branch messages, 0x0B end of a branch. +// Text values are absolute _phone string ids (0x32 publishes the list base in +// ScrWork 6655). Random RINE reply menus are script blocks +// 0C(sender, kind) 0D(value, branch)... 0E +// that the engine reads when the player replies; the chosen branch then runs +// as its own VM thread. +struct RineMessage { + int Sender; + int Kind; // 0 text, 1 sticker + int Value; // _phone string id, or sticker id +}; + +struct RineReplyOption { + int Value; + uint16_t BranchLabel; +}; + +class Rine { + public: + struct Participant { + uint8_t Avatar; + uint16_t NameStr; + }; + + void LoadParticipants(uint32_t phoneBuffer, uint16_t label); + void SelectTextList(uint16_t label); + void NewThread(int owner); + void AddMember(int participant); + void AddMessage(int sender, int kind, int value); + void ClearAll(); + void SetInput(int textValue); + void ClearInput(); + + void SetReplyMenu(uint32_t scriptBufferId, uint16_t label); + void CancelReplyMenu(); + // Reads the pending menu block; false when there is none. + bool ReadReplyMenu(int& sender, int& kind, + std::vector& options) const; + // Posts the chosen reply and starts its branch thread. + void Reply(int optionIndex); + bool IsBranchThread(const Vm::Sc3VmThread* thread) const; + void EndBranch(Vm::Sc3VmThread* thread); + // 0x0F: shows a typing pause before each branch message. Returns true when + // the message should be posted now. + bool TypingDone(); + + // The player looked at the thread: read flag set, notification cleared. + void MarkRead(); + + int Owner = 0; + std::vector Members; + std::vector Messages; + int InputText = -1; + std::vector Participants; + bool MenuPending = false; + uint64_t Version = 0; // bumped on every change, for UI relayout + + private: + uint32_t PhoneBuffer = 0; + uint32_t MenuBuffer = 0; + uint16_t MenuLabel = 0; + uint32_t BranchThreadId = UINT32_MAX; + int TypingFrames = 0; +}; + +inline Rine RineState; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/savemenu.cpp b/src/games/sg0/savemenu.cpp new file mode 100644 index 000000000..d0667a6df --- /dev/null +++ b/src/games/sg0/savemenu.cpp @@ -0,0 +1,274 @@ +#include "savemenu.h" +#include "fullwidth.h" + +#include +#include + +#include "../../profile/games/sg0/savemenu.h" +#include "../../profile/dialogue.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::SaveMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +SaveMenu::SaveMenu() : UI::SaveMenu() {} + +SaveSystem::SaveType SaveMenu::CurrentSaveType() const { + return ActiveMenuType == SaveMenuPageType::QuickLoad + ? SaveSystem::SaveType::Quick + : SaveSystem::SaveType::Full; +} + +glm::vec2 SaveMenu::EntryPos(int id) const { + const int slot = id % EntriesPerPage; + return PanelPos + EntryStart + + glm::vec2(slot % EntriesPerRow, slot / EntriesPerRow) * EntrySpacing; +} + +void SaveMenu::RefreshEntry(int id) { + EntryInfo& entry = Entries[id]; + const SaveSystem::SaveType type = CurrentSaveType(); + entry.Status = SaveSystem::GetSaveStatus(type, id); + entry.Locked = SaveSystem::GetSaveFlags(type, id) & + SaveSystem::SaveFlagsMode::WriteProtect; + entry.PlayTime.clear(); + entry.SaveDate.clear(); + if (entry.Status != 1) return; + + const uint32_t time = SaveSystem::GetSavePlayTime(type, id); + const std::string playTime = fmt::format("{:d}:{:02d}:{:02d}", time / 3600, + (time % 3600) / 60, time % 60); + char date[32]; + std::strftime(date, sizeof(date), "%Y/%m/%d %H:%M", + &SaveSystem::GetSaveDate(type, id)); + + const glm::vec2 pos = EntryPos(id); + const DialogueColorPair colors{0xFFFFFF, 0x000000}; + entry.PlayTime = TextLayoutPlainString( + FullWidth(playTime), *Profile::Dialogue::DialogueFont, TextFontSize, + colors, 1.0f, pos + PlayTimeTextOffset, TextAlignment::Left); + entry.SaveDate = TextLayoutPlainString( + FullWidth(date), *Profile::Dialogue::DialogueFont, TextFontSize, colors, + 1.0f, pos + SaveDateTextOffset, TextAlignment::Left); +} + +void SaveMenu::RefreshAllEntries() { + for (int id = 0; id < SaveSystem::MaxSaveEntries; id++) RefreshEntry(id); +} + +void SaveMenu::RefreshCurrentEntryInfo() { RefreshEntry(Cursor); } + +void SaveMenu::Choose(int id) { + const SaveSystem::SaveType type = CurrentSaveType(); + const uint8_t status = SaveSystem::GetSaveStatus(type, id); + if (status != 1 && ActiveMenuType != SaveMenuPageType::Save) { + // Nothing to load in an empty slot. + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 4, false, 0); + return; + } + ScrWork[SW_SAVEFILENO] = id; + ScrWork[SW_SAVEFILETYPE] = static_cast(type); + ScrWork[SW_SAVEFILESTATUS] = status; + SetFlag(SF_SAVEPROTECTED, SaveSystem::GetSaveFlags(type, id) & + SaveSystem::SaveFlagsMode::WriteProtect); + ChoiceMade = true; +} + +void SaveMenu::Show() { + if (State == Shown || State == Showing) return; + if (!ActiveMenuType.has_value()) { + // SW_SYSSUBMENUNO 0/3/4 -> Load/Save/QuickLoad (_Startup_win F7/F6/F5). + switch (ScrWork[SW_SYSSUBMENUNO]) { + case 3: + ActiveMenuType = SaveMenuPageType::Save; + break; + case 4: + ActiveMenuType = SaveMenuPageType::QuickLoad; + break; + default: + ActiveMenuType = SaveMenuPageType::Load; + break; + } + } + State = Showing; + if (UI::FocusedMenu != nullptr) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; + RefreshAllEntries(); +} + +void SaveMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + } else { + UI::FocusedMenu = nullptr; + } +} + +void SaveMenu::UpdateInput(float dt) { + Menu::UpdateInput(dt); + if (!IsFocused) return; + + const int page = Cursor / EntriesPerPage; + const int slot = Cursor % EntriesPerPage; + const int col = slot % EntriesPerRow; + const int row = slot / EntriesPerRow; + const int total = Pages * EntriesPerPage; + int next = Cursor; + + if (PADinputButtonWentDown & PAD1DOWN) { + next = row + 1 < RowsPerPage ? Cursor + EntriesPerRow + : ((page + 1) % Pages) * EntriesPerPage + col; + } else if (PADinputButtonWentDown & PAD1UP) { + next = row > 0 ? Cursor - EntriesPerRow + : ((page + Pages - 1) % Pages) * EntriesPerPage + + (RowsPerPage - 1) * EntriesPerRow + col; + } else if (PADinputButtonWentDown & PAD1RIGHT) { + next = col + 1 < EntriesPerRow + ? Cursor + 1 + : ((page + 1) % Pages) * EntriesPerPage + row * EntriesPerRow; + } else if (PADinputButtonWentDown & PAD1LEFT) { + next = col > 0 ? Cursor - 1 + : ((page + Pages - 1) % Pages) * EntriesPerPage + + row * EntriesPerRow + EntriesPerRow - 1; + } else if (Input::MouseWheelDeltaY < 0 || + PADinputButtonWentDown & PADcustom[8]) { + next = ((page + 1) % Pages) * EntriesPerPage + slot; + } else if (Input::MouseWheelDeltaY > 0 || + PADinputButtonWentDown & PADcustom[7]) { + next = ((page + Pages - 1) % Pages) * EntriesPerPage + slot; + } + + // Mouse: hover selects, click chooses. + const glm::vec2 frameSize = EntryFrameSprite.ScaledBounds().GetSize(); + for (int i = 0; i < EntriesPerPage; i++) { + const int id = page * EntriesPerPage + i; + const RectF bounds(EntryPos(id).x + FrameOffset.x, + EntryPos(id).y + FrameOffset.y, frameSize.x, + frameSize.y); + if (bounds.ContainsPoint(Input::CurMousePos)) { + if (Input::PrevMousePos != Input::CurMousePos) next = id; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) Choose(id); + } + } + + next = std::clamp(next, 0, total - 1); + if (next != Cursor) { + Cursor = next; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + + if (PADinputButtonWentDown & PAD1A) Choose(Cursor); + + if (PADinputButtonWentDown & PAD1Y) { + const SaveSystem::SaveType type = CurrentSaveType(); + if (SaveSystem::GetSaveStatus(type, Cursor) == 1) { + SaveSystem::SetSaveFlags(type, Cursor, + SaveSystem::GetSaveFlags(type, Cursor) ^ + SaveSystem::SaveFlagsMode::WriteProtect); + RefreshEntry(Cursor); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); + } else { + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 4, false, 0); + } + } +} + +void SaveMenu::Update(float dt) { + const int count = ScrWork[SW_SYSSUBMENUCT]; + const int subMenu = ScrWork[SW_SYSSUBMENUNO]; + const bool isSaveSubMenu = subMenu == 0 || subMenu == 3 || subMenu == 4; + + if (count > LastSubMenuCount && count > 0 && isSaveSubMenu && + (State == Hidden || State == Hiding)) { + Show(); + } else if (count < LastSubMenuCount && count < SubMenuCountMax && + (State == Shown || State == Showing)) { + Hide(); + } + LastSubMenuCount = count; + + if (State == Showing && count >= SubMenuCountMax) { + State = Shown; + IsFocused = true; + } else if (State == Hiding && count <= 0) { + State = Hidden; + ActiveMenuType = std::nullopt; + if (UI::FocusedMenu) UI::FocusedMenu->IsFocused = true; + } + + if (State == Shown && isSaveSubMenu) UpdateInput(dt); +} + +void SaveMenu::Render() { + if (State == Hidden || !ActiveMenuType.has_value()) return; + + const float alpha = + std::clamp(ScrWork[SW_SYSSUBMENUCT] / float(SubMenuCountMax), 0.0f, 1.0f); + const glm::vec4 tint(1.0f, 1.0f, 1.0f, alpha); + + Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), tint); + Renderer->DrawSprite(PanelSprite, PanelPos, tint); + Renderer->DrawSprite(HeaderSprites[static_cast(*ActiveMenuType)], + HeaderPos, tint); + + const SaveSystem::SaveType type = CurrentSaveType(); + const int page = Cursor / EntriesPerPage; + for (int i = 0; i < EntriesPerPage; i++) { + const int id = page * EntriesPerPage + i; + const EntryInfo& entry = Entries[id]; + const glm::vec2 pos = EntryPos(id); + + if (id == Cursor) { + Renderer->DrawSprite(EntryFrameSprite, pos + FrameOffset, tint); + } + if (entry.Status == 1) { + Renderer->DrawSprite( + SaveSystem::GetSaveThumbnail(type, id), + RectF(pos.x + ThumbnailOffset.x, pos.y + ThumbnailOffset.y, + ThumbnailSize.x, ThumbnailSize.y), + tint); + Profile::Dialogue::DialogueFont->DrawProcessedText( + entry.PlayTime, alpha, alpha, RendererOutlineMode::Full); + Profile::Dialogue::DialogueFont->DrawProcessedText( + entry.SaveDate, alpha, alpha, RendererOutlineMode::Full); + } else { + Renderer->DrawSprite(NoDataSprite, pos + ThumbnailOffset, tint); + } + Renderer->DrawSprite(PlayTimeLabelSprite, pos + PlayTimeLabelOffset, tint); + Renderer->DrawSprite(SaveDateLabelSprite, pos + SaveDateLabelOffset, tint); + if (entry.Locked) { + Renderer->DrawSprite(LockedSprite, pos + LockedOffset, tint); + } + } + + // "/6" + const Sprite& current = PageDigitSprites[1 + page]; + Renderer->DrawSprite(current, PageNumberPos, tint); + const float slashX = PageNumberPos.x + current.ScaledWidth(); + Renderer->DrawSprite(PageDigitSprites[0], glm::vec2(slashX, PageNumberPos.y), + tint); + + Renderer->DrawSprite(ButtonGuideSprite, ButtonGuidePos, tint); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/savemenu.h b/src/games/sg0/savemenu.h new file mode 100644 index 000000000..cd11f5f9d --- /dev/null +++ b/src/games/sg0/savemenu.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "../../ui/savemenu.h" +#include "../../text/text.h" +#include "../../data/savesystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 save / load / quick-load screen. The scripts drive it like +// CHAOS;CHILD Love Chu Chu's: SaveMenu(0, type) selects the page type, +// SW_SYSSUBMENUNO (0 load, 3 save, 4 quick load) and SW_SYSSUBMENUCT (counting +// up to SubMenuCountMax) open and close it, and a chosen slot is reported +// through SW_SAVEFILENO / SW_SAVEFILETYPE plus ChoiceMade. +class SaveMenu : public UI::SaveMenu { + public: + SaveMenu(); + void Show() override; + void Hide() override; + void UpdateInput(float dt) override; + void Update(float dt) override; + void Render() override; + void RefreshCurrentEntryInfo() override; + + private: + struct EntryInfo { + uint8_t Status = 0; + bool Locked = false; + std::vector PlayTime; + std::vector SaveDate; + }; + + SaveSystem::SaveType CurrentSaveType() const; + void RefreshEntry(int id); + void RefreshAllEntries(); + void Choose(int id); + glm::vec2 EntryPos(int id) const; + + int Cursor = 0; + int LastSubMenuCount = 0; + std::array Entries; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/sysmesbox.cpp b/src/games/sg0/sysmesbox.cpp new file mode 100644 index 000000000..f4bc50d2a --- /dev/null +++ b/src/games/sg0/sysmesbox.cpp @@ -0,0 +1,181 @@ +#include "sysmesbox.h" + +#include "../../profile/games/sg0/sysmesbox.h" +#include "../../profile/ui/sysmesbox.h" +#include "../../profile/dialogue.h" +#include "../../profile/vm.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SysMesBox; +using namespace Impacto::Profile::SG0::SysMesBox; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +void SysMesBox::Init() { + ChoiceMade = false; + MessageCount = 0; + ChoiceCount = 0; + Cursor = 0; + + FadeAnimation.DurationIn = FadeInDuration; + FadeAnimation.DurationOut = FadeOutDuration; + FadeAnimation.Direction = AnimationDirection::In; + FadeAnimation.LoopMode = AnimationLoopMode::Stop; +} + +void SysMesBox::AddMessage(Vm::BufferOffsetContext ctx) { + if (MessageCount >= static_cast(Messages.size())) return; + Vm::Sc3VmThread dummy; + dummy.IpOffset = ctx.IpOffset; + dummy.ScriptBufferId = ctx.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + Messages[MessageCount] = TextLayoutPlainLine( + &dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, + {MessageColor, OutlineColor}, 1.0f, glm::vec2(0.0f), TextAlignment::Left); + MessageWidths[MessageCount] = GetTextWidth(Messages[MessageCount]); + MessageCount++; +} + +void SysMesBox::AddChoice(Vm::BufferOffsetContext ctx) { + if (ChoiceCount >= static_cast(Choices.size())) return; + Vm::Sc3VmThread dummy; + dummy.IpOffset = ctx.IpOffset; + dummy.ScriptBufferId = ctx.BufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + Choices[ChoiceCount] = TextLayoutPlainLine( + &dummy, 255, *Profile::Dialogue::DialogueFont, TextFontSize, + {ChoiceColor, OutlineColor}, 1.0f, glm::vec2(0.0f), TextAlignment::Left); + if (Choices[ChoiceCount].empty()) return; + ChoiceWidths[ChoiceCount] = GetTextWidth(Choices[ChoiceCount]); + ChoiceCount++; +} + +void SysMesBox::Show() { + // Messages: centred lines around TextMiddleY; choices: one centred row below. + const float blockHeight = MessageCount * TextLineHeight; + const float firstLineY = TextMiddleY - blockHeight / 2.0f; + for (int i = 0; i < MessageCount; i++) { + if (Messages[i].empty()) continue; + const glm::vec2 target(TextX - MessageWidths[i] / 2.0f, + firstLineY + i * TextLineHeight); + const glm::vec2 offset = target - Messages[i][0].Position; + for (ProcessedTextGlyph& glyph : Messages[i]) glyph.Move(offset); + } + + float rowWidth = ChoiceSpacing * std::max(ChoiceCount - 1, 0); + for (int i = 0; i < ChoiceCount; i++) rowWidth += ChoiceWidths[i]; + float x = TextX - rowWidth / 2.0f; + const float choiceY = firstLineY + blockHeight + TextMarginY; + for (int i = 0; i < ChoiceCount; i++) { + const glm::vec2 offset = glm::vec2(x, choiceY) - Choices[i][0].Position; + for (ProcessedTextGlyph& glyph : Choices[i]) glyph.Move(offset); + ChoiceBounds[i] = RectF(x - ChoicePadding.x, choiceY - ChoicePadding.y, + ChoiceWidths[i] + 2 * ChoicePadding.x, + TextFontSize + 2 * ChoicePadding.y); + x += ChoiceWidths[i] + ChoiceSpacing; + } + SetCursor(0); + + FadeAnimation.StartIn(); + State = Showing; + if (UI::FocusedMenu != nullptr) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + IsFocused = true; + UI::FocusedMenu = this; +} + +void SysMesBox::Hide() { + FadeAnimation.StartOut(); + State = Hiding; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + } else { + UI::FocusedMenu = nullptr; + } + IsFocused = false; +} + +void SysMesBox::SetCursor(int choice) { + Cursor = choice; + for (int i = 0; i < ChoiceCount; i++) { + const uint32_t color = i == Cursor ? ChoiceHighlightColor : ChoiceColor; + for (ProcessedTextGlyph& glyph : Choices[i]) glyph.Colors.TextColor = color; + } +} + +void SysMesBox::Choose(int choice) { + ScrWork[SW_SYSSEL] = choice; + ChoiceMade = true; +} + +void SysMesBox::UpdateInput(float dt) { + if (ChoiceCount == 0 || ChoiceMade) return; + + int next = Cursor; + if (PADinputButtonWentDown & (PAD1LEFT | PAD1UP)) { + next = (Cursor + ChoiceCount - 1) % ChoiceCount; + } else if (PADinputButtonWentDown & (PAD1RIGHT | PAD1DOWN)) { + next = (Cursor + 1) % ChoiceCount; + } + for (int i = 0; i < ChoiceCount; i++) { + if (!ChoiceBounds[i].ContainsPoint(Input::CurMousePos)) continue; + if (Input::PrevMousePos != Input::CurMousePos) next = i; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + SetCursor(i); + Choose(i); + return; + } + } + if (next != Cursor) { + SetCursor(next); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + + if (PADinputButtonWentDown & PAD1A) { + Choose(Cursor); + } else if (PADinputButtonWentDown & PAD1B) { + // Cancel answers with the last choice ("No" in every two-choice box). + SetCursor(ChoiceCount - 1); + Choose(ChoiceCount - 1); + } +} + +void SysMesBox::Update(float dt) { + FadeAnimation.Update(dt); + if (State == Showing && FadeAnimation.IsIn()) State = Shown; + if (State == Hiding && FadeAnimation.IsOut()) State = Hidden; + if (State == Shown && IsFocused) UpdateInput(dt); +} + +void SysMesBox::Render() { + if (State == Hidden) return; + const float alpha = FadeAnimation.Progress; + + Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), + glm::vec4(1.0f, 1.0f, 1.0f, alpha * BackgroundOpacity)); + for (int i = 0; i < MessageCount; i++) { + Profile::Dialogue::DialogueFont->DrawProcessedText( + Messages[i], alpha, RendererOutlineMode::Full); + } + for (int i = 0; i < ChoiceCount; i++) { + Profile::Dialogue::DialogueFont->DrawProcessedText( + Choices[i], alpha, RendererOutlineMode::Full); + } +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/sysmesbox.h b/src/games/sg0/sysmesbox.h new file mode 100644 index 000000000..b1c687f93 --- /dev/null +++ b/src/games/sg0/sysmesbox.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "../../ui/sysmesbox.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 system message: a full-screen backdrop (gsysmes.dds) with the +// message lines centred on it and the script's choice strings in one row +// underneath. SystemMes(2, kind) chooses 0 = OK (no choices, any confirm +// closes), 1 = two choices, 255 = no input (progress messages); the chosen +// index goes to SW_SYSSEL. +class SysMesBox : public UI::SysMesBox { + public: + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + + void Init() override; + void AddMessage(Vm::BufferOffsetContext ctx) override; + void AddChoice(Vm::BufferOffsetContext ctx) override; + + private: + void SetCursor(int choice); + void Choose(int choice); + + int Cursor = 0; + std::array ChoiceBounds; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/systemmenu.cpp b/src/games/sg0/systemmenu.cpp new file mode 100644 index 000000000..b79e87ad8 --- /dev/null +++ b/src/games/sg0/systemmenu.cpp @@ -0,0 +1,166 @@ +#include "systemmenu.h" + +#include + +#include "../../profile/games/sg0/systemmenu.h" +#include "../../profile/ui/systemmenu.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::SystemMenu; +using namespace Impacto::Profile::SystemMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +SystemMenu::SystemMenu() { + const RectF viewport = Window->GetViewport(); + ScreenCap.Sheet = SpriteSheet(viewport.Width, viewport.Height); + Texture tex; + tex.LoadSolidColor(static_cast(viewport.Width), + static_cast(viewport.Height), 0x000000); + ScreenCap.Sheet.Texture = tex.Submit(); + ScreenCap.Bounds.SetSize(viewport.GetSize()); +} + +static RectF ButtonRect(int id, float scale = 1.0f) { + const Sprite& sprite = MenuEntriesSprites[id]; + const glm::vec2 size = + glm::vec2(sprite.ScaledWidth(), sprite.ScaledHeight()) * scale; + return RectF(EntryCenters[id].x - size.x / 2.0f, + EntryCenters[id].y - size.y / 2.0f, size.x, size.y); +} + +void SystemMenu::Init() { + // Capture the game screen for the monitor before the menu covers it. + SetFlag(SF_SYSTEMMENUCAPTURE, true); +} + +void SystemMenu::Show() { + if (State == Shown || State == Showing) return; + State = Showing; + // F2 / F5-F7 open their screen "directly": the script raises SW_SYSMENUCT + // with SF_SYSTEMMENUDIRECT set, and the menu itself stays out of sight. + OpenedAsDirect = GetFlag(SF_SYSTEMMENUDIRECT); + if (OpenedAsDirect) return; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; +} + +void SystemMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + if (OpenedAsDirect) return; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +void SystemMenu::UpdateInput(float dt) { + // Submenus (save, backlog, ...) take input while they are open. + if (!IsFocused || ScrWork[SW_SYSSUBMENUCT] > 0) return; + + const int count = static_cast(NavOrder.size()); + int next = Cursor; + if (PADinputButtonRepeatAccelDown & (PAD1UP | PAD1LEFT)) { + next = (Cursor + count - 1) % count; + } else if (PADinputButtonRepeatAccelDown & (PAD1DOWN | PAD1RIGHT)) { + next = (Cursor + 1) % count; + } + + for (int i = 0; i < count; i++) { + if (!ButtonRect(NavOrder[i]).ContainsPoint(Input::CurMousePos)) continue; + if (Input::PrevMousePos != Input::CurMousePos) next = i; + } + if (next != Cursor) { + Cursor = next; + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + + const bool mouseOnCursor = + ButtonRect(NavOrder[Cursor]).ContainsPoint(Input::CurMousePos); + if (PADinputButtonWentDown & PAD1A || + (Input::MouseButtonWentDown[SDL_BUTTON_LEFT] && mouseOnCursor)) { + ScrWork[SW_SYSMENUCNO] = NavOrder[Cursor]; + ChoiceMade = true; + } + // A click outside every button must not reach the script as a confirm. + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT] && !mouseOnCursor) { + PADinputButtonWentDown &= ~PAD1A; + PADinputMouseWentDown &= ~PAD1A; + } +} + +void SystemMenu::Update(float dt) { + const int count = ScrWork[SW_SYSMENUCT]; + if (count > LastSysMenuCt && count > 0 && !GetFlag(SF_TITLEMODE)) { + Show(); + } else if (count < LastSysMenuCt && count < CountMax) { + Hide(); + } + LastSysMenuCt = count; + + if (State == Showing && count >= CountMax) { + State = Shown; + IsFocused = UI::FocusedMenu == this; + } else if (State == Hiding && count <= 0) { + State = Hidden; + } + + if (State == Shown && !OpenedAsDirect) UpdateInput(dt); +} + +void SystemMenu::Render() { + if (State == Hidden || OpenedAsDirect || GetFlag(SF_TITLEMODE)) return; + + const float alpha = std::clamp( + ScrWork[SW_SYSMENUCT] / static_cast(CountMax), 0.0f, 1.0f); + const glm::vec4 tint(1.0f, 1.0f, 1.0f, alpha); + + Renderer->DrawSprite(BackgroundSprite, glm::vec2(0.0f), tint); + Renderer->DrawSprite(ScreenCap, MonitorScreen, tint); + Renderer->DrawSprite(GlassSprite, MonitorScreen, + glm::vec4(1.0f, 1.0f, 1.0f, alpha * GlassOpacity)); + Renderer->DrawSprite(RingSprite, RingPos, tint); + + // Hide the buttons while a submenu covers the screen. + if (ScrWork[SW_SYSSUBMENUCT] > 0) return; + const int focused = NavOrder[Cursor]; + for (int id = 0; id < MenuEntriesNum; id++) { + const bool isFocused = id == focused && State == Shown; + if (isFocused) { + const Sprite& glow = MenuEntriesHSprites[id]; + const glm::vec2 size = + glm::vec2(glow.ScaledWidth(), glow.ScaledHeight()) * FocusScale; + Renderer->DrawSprite( + glow, + RectF(EntryCenters[id].x - size.x / 2.0f, + EntryCenters[id].y - size.y / 2.0f, size.x, size.y), + tint); + } + const float brightness = isFocused ? 1.0f : UnfocusedBrightness; + Renderer->DrawSprite(MenuEntriesSprites[id], + ButtonRect(id, isFocused ? FocusScale : 1.0f), + glm::vec4(glm::vec3(brightness), alpha)); + } +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/systemmenu.h b/src/games/sg0/systemmenu.h new file mode 100644 index 000000000..b671a2f5b --- /dev/null +++ b/src/games/sg0/systemmenu.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../../ui/menu.h" +#include "../../spritesheet.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 system menu (menuchip.dds): the game's screen capture inside a +// monitor frame and a ring of icon buttons. _Startup_win drives it with +// SystemMenu(0), SW_SYSMENUCT counting 0..96 in steps of 3 and SystemMenu(1) +// each frame; the chosen button id goes to SW_SYSMENUCNO, which the script +// dispatches with a 12-entry jump table (0 Backlog, 1 Quick Save, 2 Save, +// 3 Load, 4 Quick Load, 5 TIPS, 6 Config, 7 Help, 8 Title, 9 Full Screen, +// 10 Exit Game, 11 Close Menu). +class SystemMenu : public Menu { + public: + SystemMenu(); + + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + void Init() override; + + Sprite ScreenCap; + + private: + int Cursor = 0; // index into the navigation order + bool OpenedAsDirect = false; + int LastSysMenuCt = 0; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/tipsmenu.cpp b/src/games/sg0/tipsmenu.cpp new file mode 100644 index 000000000..15fca2264 --- /dev/null +++ b/src/games/sg0/tipsmenu.cpp @@ -0,0 +1,342 @@ +#include "tipsmenu.h" + +#include + +#include "fullwidth.h" +#include "../../profile/games/sg0/tipsmenu.h" +#include "../../profile/ui/tipsmenu.h" +#include "../../profile/dialogue.h" +#include "../../profile/scriptvars.h" +#include "../../profile/vm.h" +#include "../../data/tipssystem.h" +#include "../../renderer/renderer.h" +#include "../../ui/ui.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../audio/audiosystem.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::SG0::TipsMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +namespace { +constexpr int SubMenuNo = 2; +// Tip record strings: 0 category, 1 name, 2 sort key, 3 list name, 4.. note. +constexpr int CategoryString = 0; +constexpr int NameString = 1; +constexpr int ListNameString = 3; +constexpr int NoteString = 4; + +std::vector LayoutStream(Vm::Sc3Stream stream, + glm::vec2 pos, float size, + uint32_t color) { + return TextLayoutPlainLine(stream, 255, *Profile::Dialogue::DialogueFont, + size, {color, 0}, 1.0f, pos, TextAlignment::Left); +} + +std::vector LayoutAscii(std::string_view text, + glm::vec2 pos, float size, + uint32_t color) { + return TextLayoutPlainString(FullWidth(text), + *Profile::Dialogue::DialogueFont, size, + {color, 0}, 1.0f, pos, TextAlignment::Left); +} + +bool InTab(const TipsSystem::TipsDataRecord& record, int tab) { + switch (tab) { + case 1: + return !record.IsLocked; + case 2: + return !record.IsLocked && record.IsUnread; + case 3: + return !record.IsLocked && record.IsNew; + default: + return true; + } +} +} // namespace + +void TipsMenu::Init() { + TipsSystem::UpdateTipRecords(); + BuildList(); +} + +void TipsMenu::BuildList() { + const auto* records = TipsSystem::GetTipRecords(); + TabCounts.fill(0); + if (records) { + for (const auto& record : *records) { + for (int tab = 0; tab < TabCount; tab++) { + if (InTab(record, tab)) TabCounts[tab]++; + } + } + } + + Items.clear(); + if (!records) return; + for (const auto& record : *records) { + if (!InTab(record, CurrentTab)) continue; + const float y = ListTop + Items.size() * RowHeight; + ListItem item{record.Id, {}}; + item.Text = LayoutAscii(fmt::format("{:03d}", record.Id + 1), + glm::vec2(ListNumberX, y), ListFontSize, TextColor); + std::vector name = + record.IsLocked + ? LayoutAscii("???", glm::vec2(ListNameX, y), ListFontSize, + LockedColor) + : LayoutStream( + TipsSystem::GetTextStringStream(record.Id, ListNameString), + glm::vec2(ListNameX, y), ListFontSize, TextColor); + SquishText(name, ListNameMaxWidth, ListNameX); + item.Text.insert(item.Text.end(), name.begin(), name.end()); + Items.push_back(std::move(item)); + } + Cursor = + std::clamp(Cursor, 0, std::max(0, static_cast(Items.size()) - 1)); + ClampListScroll(); +} + +void TipsMenu::SetTab(int tab) { + if (tab == CurrentTab || TabCounts[tab] == 0) return; + CurrentTab = tab; + Cursor = 0; + ListScroll = 0; + BuildList(); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); +} + +void TipsMenu::ClampListScroll() { + if (Cursor < ListScroll) ListScroll = Cursor; + if (Cursor >= ListScroll + VisibleRows) ListScroll = Cursor - VisibleRows + 1; + ListScroll = std::clamp( + ListScroll, 0, std::max(0, static_cast(Items.size()) - VisibleRows)); +} + +void TipsMenu::ShowTip(int tipId) { + TipsSystem::TipsDataRecord* record = TipsSystem::GetTipRecord(tipId); + if (!record || record->IsLocked) { + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 4, false, 0); + return; + } + DisplayedTip = tipId; + TipsSystem::SetTipUnreadState(tipId, false); + TipsSystem::SetTipNewState(tipId, false); + + NumberGlyphs = LayoutAscii(fmt::format("{:03d}", tipId + 1), NumberPos, + HeaderFontSize, TextColor); + CategoryGlyphs = + LayoutStream(TipsSystem::GetTextStringStream(tipId, CategoryString), + CategoryPos, HeaderFontSize, TextColor); + NameGlyphs = LayoutStream(TipsSystem::GetTextStringStream(tipId, NameString), + NamePos, NameFontSize, TextColor); + SquishText(NameGlyphs, NameMaxWidth, NamePos.x); + + auto [buffers, tipsBufferId] = TipsSystem::GetTipsScriptBufferCtx(); + Vm::Sc3VmThread dummy; + dummy.ScriptBufferId = tipsBufferId; + dummy.UseMSBBuffers = Profile::Vm::UseMsbStrings; + dummy.SetStringIp(TipsSystem::GetTextStringStream(tipId, NoteString).Data()); + TextPage.Clear(); + TextPage.AddString(&dummy); + NoteScroll = 0.0f; + NoteScrollMax = + std::max(0.0f, TextPage.Bounds.Bottom() - NoteBounds.Bottom()); + + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 2, false, 0); +} + +void TipsMenu::Show() { + if (State == Shown || State == Showing) return; + State = Showing; + TipsSystem::UpdateTipRecords(); + BuildList(); + DisplayedTip = -1; + if (UI::FocusedMenu != nullptr && UI::FocusedMenu != this) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + UI::FocusedMenu = this; +} + +void TipsMenu::Hide() { + if (State == Hidden || State == Hiding) return; + State = Hiding; + IsFocused = false; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + LastFocusedMenu = nullptr; + } else { + UI::FocusedMenu = nullptr; + } +} + +void TipsMenu::UpdateInput(float dt) { + if (!IsFocused) return; + const int count = static_cast(Items.size()); + + int next = Cursor; + if (PADinputButtonRepeatAccelDown & PAD1UP) next = std::max(Cursor - 1, 0); + if (PADinputButtonRepeatAccelDown & PAD1DOWN) { + next = std::min(Cursor + 1, std::max(count - 1, 0)); + } + if (PADinputButtonRepeatAccelDown & PAD1LEFT) { + next = std::max(Cursor - VisibleRows, 0); + } + if (PADinputButtonRepeatAccelDown & PAD1RIGHT) { + next = std::min(Cursor + VisibleRows, std::max(count - 1, 0)); + } + + // Mouse: hover a row to select it, click to open; wheel scrolls the list + // (or the note while the pointer is over it). + const glm::vec2 mouse = Input::CurMousePos; + const bool overNote = NoteBounds.ContainsPoint(mouse); + for (int row = 0; row < VisibleRows && ListScroll + row < count; row++) { + const RectF bounds(ListBounds.X, ListTop + row * RowHeight - RowPadding, + ListBounds.Width, RowHeight); + if (!bounds.ContainsPoint(mouse)) continue; + if (Input::PrevMousePos != mouse) next = ListScroll + row; + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT]) { + Cursor = ListScroll + row; + ShowTip(Items[Cursor].TipId); + } + } + if (!overNote && Input::MouseWheelDeltaY != 0) { + next = std::clamp(Cursor - static_cast(Input::MouseWheelDeltaY), 0, + std::max(count - 1, 0)); + } + if (next != Cursor) { + Cursor = next; + ClampListScroll(); + Audio::PlayInGroup(Audio::ACG_SE, "sysse", 1, false, 0); + } + + for (int tab = 0; tab < TabCount; tab++) { + if (Input::MouseButtonWentDown[SDL_BUTTON_LEFT] && + TabBounds[tab].ContainsPoint(mouse)) { + SetTab(tab); + } + } + if (PADinputButtonWentDown & (PAD1R1 | PAD1L1)) { + const int step = PADinputButtonWentDown & PAD1R1 ? 1 : TabCount - 1; + int tab = (CurrentTab + step) % TabCount; + while (TabCounts[tab] == 0 && tab != CurrentTab) { + tab = (tab + step) % TabCount; + } + SetTab(tab); + } + + if (PADinputButtonWentDown & PAD1A && count > 0) ShowTip(Items[Cursor].TipId); + + if (DisplayedTip >= 0) { + float scroll = 0.0f; + if (PADinputButtonIsDown & PAD1UP_RS) scroll -= NoteScrollSpeed * dt; + if (PADinputButtonIsDown & PAD1DOWN_RS) scroll += NoteScrollSpeed * dt; + if (overNote) scroll -= Input::MouseWheelDeltaY * RowHeight; + NoteScroll = std::clamp(NoteScroll + scroll, 0.0f, NoteScrollMax); + } +} + +void TipsMenu::Update(float dt) { + const int count = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo ? ScrWork[SW_SYSSUBMENUCT] : 0; + if (count > LastSubMenuCount && count > 0) { + Show(); + } else if (count < LastSubMenuCount) { + Hide(); + } + LastSubMenuCount = count; + + if (State == Showing && count >= CountMax) { + State = Shown; + IsFocused = UI::FocusedMenu == this; + } else if (State == Hiding && count <= 0) { + State = Hidden; + } + if (State == Shown) UpdateInput(dt); +} + +void TipsMenu::Render() { + if (State == Hidden) return; + const float alpha = + ScrWork[SW_SYSSUBMENUNO] == SubMenuNo + ? std::clamp(ScrWork[SW_SYSSUBMENUCT] / float(CountMax), 0.0f, 1.0f) + : 0.0f; + const glm::vec4 tint(1.0f, 1.0f, 1.0f, alpha); + Fonts::Font& font = *Profile::Dialogue::DialogueFont; + + Renderer->DrawSprite(Profile::TipsMenu::BackgroundSprite, glm::vec2(0.0f), + tint); + // The TIPS emblem sits behind the (translucent) panels. + Renderer->DrawSprite(HeaderSprite, HeaderPos, tint); + Renderer->DrawSprite(HeaderIconSprite, HeaderIconPos, tint); + Renderer->DrawSprite(PanelsSprite, glm::vec2(0.0f), tint); + + for (int tab = 0; tab < TabCount; tab++) { + const RectF& cell = TabBounds[tab]; + const Sprite& label = tab == CurrentTab ? TabSelectedSprites[tab] + : TabCounts[tab] > 0 ? TabSprites[tab] + : TabDisabledSprites[tab]; + Renderer->DrawSprite( + label, + glm::vec2(cell.X + (cell.Width - label.ScaledWidth()) / 2.0f, + cell.Y + (cell.Height - label.ScaledHeight()) / 2.0f), + tint); + } + + // List: rows ListScroll .. ListScroll + VisibleRows, shifted to the top. + const float listShift = -ListScroll * RowHeight; + Renderer->EnableScissor(); + Renderer->SetScissorRect(ListBounds); + for (int i = ListScroll; + i < std::min(static_cast(Items.size()), ListScroll + VisibleRows); + i++) { + const float y = ListTop + i * RowHeight + listShift; + if (i == Cursor) { + Renderer->DrawSprite(SelectionSprite, + glm::vec2(ListBounds.X, y - RowPadding), tint); + } + DrawTextAt(Items[i].Text, glm::vec2(0.0f, listShift), alpha, + RendererOutlineMode::None); + const auto* record = TipsSystem::GetTipRecord(Items[i].TipId); + if (record && !record->IsLocked && record->IsNew) { + Renderer->DrawSprite(NewBadgeSprite, + glm::vec2(NewBadgeX, y - RowPadding + 6.0f), tint); + } + } + Renderer->DisableScissor(); + + if (DisplayedTip >= 0) { + font.DrawProcessedText(NumberGlyphs, alpha, alpha, + RendererOutlineMode::None); + font.DrawProcessedText(CategoryGlyphs, alpha, alpha, + RendererOutlineMode::None); + font.DrawProcessedText(NameGlyphs, alpha, alpha, RendererOutlineMode::None); + + Renderer->EnableScissor(); + Renderer->SetScissorRect(NoteBounds); + DrawTextAt(TextPage.Glyphs, glm::vec2(0.0f, -NoteScroll), alpha, + RendererOutlineMode::None); + Renderer->DisableScissor(); + + if (NoteScrollMax > 0.0f) { + const float travel = NoteBounds.Height - NoteThumbSprite.ScaledHeight(); + Renderer->DrawSprite( + NoteThumbSprite, + glm::vec2(NoteThumbX, + NoteBounds.Y + travel * (NoteScroll / NoteScrollMax)), + tint); + } + } + + Renderer->DrawSprite(GuideSprite, GuidePos, tint); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/tipsmenu.h b/src/games/sg0/tipsmenu.h new file mode 100644 index 000000000..63471b40f --- /dev/null +++ b/src/games/sg0/tipsmenu.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +#include "../../ui/tipsmenu.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 TIPS (sysm_tips.dds, tipschips.dds): a tabbed list of tips on +// the left (ALL / ACQUIRED / UNREAD / NEW) and the chosen tip on the right. +// _Startup_win opens it with SW_SYSSUBMENUNO 2 and SW_SYSSUBMENUCT counting +// 0..32, runs Tips(2) every frame until Back, then counts down. +class TipsMenu : public UI::TipsMenu { + public: + void Init() override; + void Show() override; + void Hide() override; + void Update(float dt) override; + void UpdateInput(float dt) override; + void Render() override; + + private: + enum Tab { All, Acquired, Unread, New, TabCount }; + struct ListItem { + int TipId; + std::vector Text; + }; + + void BuildList(); + void SetTab(int tab); + void ShowTip(int tipId); + void ClampListScroll(); + + int CurrentTab = All; + std::array TabCounts{}; + std::vector Items; + int Cursor = 0; + int ListScroll = 0; // first visible row + + int DisplayedTip = -1; + std::vector NumberGlyphs; + std::vector CategoryGlyphs; + std::vector NameGlyphs; + float NoteScroll = 0.0f; + float NoteScrollMax = 0.0f; + int LastSubMenuCount = 0; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/titlemenu.cpp b/src/games/sg0/titlemenu.cpp new file mode 100644 index 000000000..991386d2e --- /dev/null +++ b/src/games/sg0/titlemenu.cpp @@ -0,0 +1,205 @@ +#include "titlemenu.h" + +#include "../../profile/ui/titlemenu.h" +#include "../../profile/games/sg0/titlemenu.h" +#include "../../profile/scriptvars.h" +#include "../../renderer/renderer.h" +#include "../../mem.h" +#include "../../inputsystem.h" +#include "../../vm/interface/input.h" +#include "../../games/cc/titlemenu.h" // TitleMenuMode::PS4 + +namespace Impacto { +namespace UI { +namespace SG0 { + +using namespace Impacto::Profile::TitleMenu; +using namespace Impacto::Profile::SG0::TitleMenu; +using namespace Impacto::Profile::ScriptVars; +using namespace Impacto::Vm::Interface; + +// SW_TITLECUR1 values the SG0 title script dispatches on. +constexpr int MainItemIds[] = {0, -1, -1, 30, 40}; +constexpr int SubItemIds[SubItemsNum] = {10, 11, 20, 21, 22, 23, 24}; + +void TitleMenu::MenuButtonOnClick(Widgets::Button* target) { + target->Hovered = false; + ScrWork[SW_TITLECUR1] = target->Id; + SetFlag(SF_TITLEEND, 1); + AllowsScriptInput = true; + if (CurrentSubMenu) { + CurrentSubMenu->Hide(); + CurrentSubMenu = nullptr; + } +} + +void TitleMenu::OpenSubMenu(Widgets::Group* subMenu, Widgets::Button* parent) { + parent->Hovered = false; + CurrentSubMenu = subMenu; + subMenu->Show(); + CurrentlyFocusedElement = subMenu->GetFirstFocusableChild(); + if (CurrentlyFocusedElement) CurrentlyFocusedElement->HasFocus = true; + AllowsScriptInput = false; +} + +TitleMenu::TitleMenu() { + MainItems = new Widgets::Group(this); + ContinueItems = new Widgets::Group(this); + ContinueItems->WrapFocus = false; + ExtraItems = new Widgets::Group(this); + ExtraItems->WrapFocus = false; + + auto onClick = [this](Widgets::Button* btn) { MenuButtonOnClick(btn); }; + const Sprite nullSprite{}; + + Widgets::Button* mainButtons[5]; + for (int i = 0; i < 5; i++) { + const glm::vec2 pos = ItemPos + glm::vec2(0.0f, i * ItemSpacing); + mainButtons[i] = + new Widgets::Button(MainItemIds[i], MenuEntriesSprites[i], + MenuEntriesHSprites[i], nullSprite, pos); + mainButtons[i]->OnClickHandler = onClick; + MainItems->Add(mainButtons[i], FDIR_DOWN); + } + NewGame = mainButtons[0]; + mainButtons[1]->OnClickHandler = [this](Widgets::Button* btn) { + OpenSubMenu(ContinueItems, btn); + }; + mainButtons[2]->OnClickHandler = [this](Widgets::Button* btn) { + OpenSubMenu(ExtraItems, btn); + }; + + // Sub items hang to the right of their parent item. + for (int i = 0; i < SubItemsNum; i++) { + const bool isContinue = i < 2; + const int parentIndex = isContinue ? 1 : 2; + const int row = isContinue ? i : i - 2; + const glm::vec2 pos = + ItemPos + glm::vec2(SubItemOffsetX, + parentIndex * ItemSpacing + row * SubItemSpacing); + auto* button = new Widgets::Button(SubItemIds[i], SubItemSprites[i], + SubItemHSprites[i], nullSprite, pos); + button->OnClickHandler = onClick; + (isContinue ? ContinueItems : ExtraItems)->Add(button, FDIR_DOWN); + } +} + +void TitleMenu::Show() { + if (State == Shown) return; + State = Shown; + if (UI::FocusedMenu != nullptr) { + LastFocusedMenu = UI::FocusedMenu; + LastFocusedMenu->IsFocused = false; + } + IsFocused = true; + UI::FocusedMenu = this; + AllowsScriptInput = true; + if (PressToStartAnimation.State == AnimationState::Stopped) { + PressToStartAnimation.StartIn(); + } +} + +void TitleMenu::Hide() { + if (State == Hidden) return; + State = Hidden; + MainItems->Hide(); + ContinueItems->Hide(); + ExtraItems->Hide(); + CurrentSubMenu = nullptr; + if (LastFocusedMenu != nullptr) { + UI::FocusedMenu = LastFocusedMenu; + LastFocusedMenu->IsFocused = true; + } else { + UI::FocusedMenu = nullptr; + } + IsFocused = false; + AllowsScriptInput = true; +} + +void TitleMenu::UpdateInput(float dt) { + // An EXTRA screen, TIPS or the config opened from here holds the focus; + // their key presses must not also click the title's items. + if (!IsFocused) return; + Menu::UpdateInput(dt); + if (CurrentSubMenu) { + CurrentSubMenu->UpdateInput(dt); + if (PADinputButtonWentDown & PAD1B || PADinputMouseWentDown & PAD1B) { + // Back out of the sub menu to the main items. + CurrentSubMenu->Hide(); + CurrentSubMenu = nullptr; + AllowsScriptInput = true; + } + } else { + MainItems->UpdateInput(dt); + } +} + +void TitleMenu::Update(float dt) { + UpdateInput(dt); + PressToStartAnimation.Update(dt); + + if (GetFlag(SF_TITLEMODE)) { + Show(); + } else { + Hide(); + } + if (State == Hidden) return; + + using namespace CC::TitleMenuMode::PS4; + switch (ScrWork[SW_TITLEMODE]) { + case PressToStart: + PressToStartAnimation.DurationIn = PressToStartAnimDurationIn; + PressToStartAnimation.DurationOut = PressToStartAnimDurationOut; + break; + case StartTransition: + PressToStartAnimation.DurationIn = PressToStartAnimFastDurationIn; + PressToStartAnimation.DurationOut = PressToStartAnimFastDurationOut; + break; + case Main: + MainItems->Update(dt); + if (CurrentSubMenu) CurrentSubMenu->Update(dt); + if (MainItems->VisibilityState == Hidden) { + MainItems->Show(); + CurrentlyFocusedElement = NewGame; + NewGame->HasFocus = true; + } + break; + } +} + +void TitleMenu::Render() { + if (State == Hidden || !GetFlag(SF_TITLEMODE)) return; + + using namespace CC::TitleMenuMode::PS4; + switch (ScrWork[SW_TITLEMODE]) { + case PressToStart: + case StartTransition: + DrawLogo(1.0f); + DrawStartButton(); + break; + case Main: + DrawLogo(1.0f); + MainItems->Render(); + if (CurrentSubMenu) CurrentSubMenu->Render(); + break; + case InitialFade: + DrawLogo(ScrWork[SW_TITLEDISPCT] / 32.0f); + break; + } +} + +void TitleMenu::DrawLogo(float opacity) { + Renderer->DrawSprite( + LogoSprite, LogoPos, + glm::vec4(1.0f, 1.0f, 1.0f, std::clamp(opacity, 0.0f, 1.0f))); +} + +void TitleMenu::DrawStartButton() { + glm::vec4 col(1.0f); + col.a = glm::smoothstep(0.0f, 1.0f, PressToStartAnimation.Progress); + Renderer->DrawSprite(PressToStartSprite, PressToStartPos, col); +} + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/games/sg0/titlemenu.h b/src/games/sg0/titlemenu.h new file mode 100644 index 000000000..a4cafc012 --- /dev/null +++ b/src/games/sg0/titlemenu.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../../animation.h" +#include "../../ui/menu.h" +#include "../../ui/widgets/group.h" +#include "../../ui/widgets/button.h" + +namespace Impacto { +namespace UI { +namespace SG0 { + +// STEINS;GATE 0 title menu. The script drives it through the same title +// protocol as CHAOS;CHILD (SW_TITLEMODE, SW_TITLEDISPCT, SF_TITLEEND) and reads +// the chosen item from SW_TITLECUR1: 0 NEW GAME, 10 LOAD, 11 QUICK LOAD, +// 20-24 CLEAR LIST / CG LIBRARY / SOUND LIBRARY / MOVIE LIBRARY / TIPS, +// 30 CONFIG, 40 HELP. The background is the intro movie the script plays. +class TitleMenu : public Menu { + public: + TitleMenu(); + void Show() override; + void Hide() override; + void UpdateInput(float dt) override; + void Update(float dt) override; + void Render() override; + + Animation PressToStartAnimation; + + private: + void MenuButtonOnClick(Widgets::Button* target); + void OpenSubMenu(Widgets::Group* subMenu, Widgets::Button* parent); + void DrawLogo(float opacity); + void DrawStartButton(); + + Widgets::Group* MainItems; + Widgets::Group* ContinueItems; + Widgets::Group* ExtraItems; + Widgets::Group* CurrentSubMenu = nullptr; + Widgets::Button* NewGame; +}; + +} // namespace SG0 +} // namespace UI +} // namespace Impacto diff --git a/src/profile/fonts.cpp b/src/profile/fonts.cpp index c47bb642b..45aa61e67 100644 --- a/src/profile/fonts.cpp +++ b/src/profile/fonts.cpp @@ -86,10 +86,15 @@ void LoadFonts() { } if (fontType == SingleSheet) { - Fonts[name] = new SingleSheetFont( + auto* font = new SingleSheetFont( *bitmapEmWidth, *bitmapEmHeight, foregroundOpacityCurve, outlineOpacityCurve, sheet, gridSize, EnsureGetAdvanceWidths(name, *bitmapEmWidth)); + std::vector leftBearings; + if (TryGetMember("LeftBearingsTable", leftBearings)) { + font->SetLeftBearings(std::move(leftBearings)); + } + Fonts[name] = font; } else { assert(fontType == EdgeDetectedSingleSheet); diff --git a/src/profile/games/sg0/backlogmenu.cpp b/src/profile/games/sg0/backlogmenu.cpp new file mode 100644 index 000000000..a0bf1b82c --- /dev/null +++ b/src/profile/games/sg0/backlogmenu.cpp @@ -0,0 +1,31 @@ +#include "backlogmenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/backlogmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace BacklogMenu { + +void Configure() { + PanelPos = EnsureGetMember("PanelPos"); + ShadeOpacity = EnsureGetMember("ShadeOpacity"); + HighlightBounds = EnsureGetMember("HighlightBounds"); + const uint32_t color = EnsureGetMember("HighlightColor"); + HighlightColor = + glm::vec3((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF) / + 255.0f; + HighlightOpacity = EnsureGetMember("HighlightOpacity"); + + auto drawType = EnsureGetMember("DrawType"); + UI::BacklogMenuPtr = new UI::SG0::BacklogMenu(); + UI::Menus[drawType].push_back(UI::BacklogMenuPtr); +} + +} // namespace BacklogMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/backlogmenu.h b/src/profile/games/sg0/backlogmenu.h new file mode 100644 index 000000000..8ad3b34dd --- /dev/null +++ b/src/profile/games/sg0/backlogmenu.h @@ -0,0 +1,21 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace BacklogMenu { + +void Configure(); + +inline glm::vec2 PanelPos; +inline float ShadeOpacity; // dims the scene around the panel +inline RectF HighlightBounds; // horizontal extent of the focus highlight +inline glm::vec3 HighlightColor; +inline float HighlightOpacity; + +} // namespace BacklogMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/clearlistmenu.cpp b/src/profile/games/sg0/clearlistmenu.cpp new file mode 100644 index 000000000..95ec0a19d --- /dev/null +++ b/src/profile/games/sg0/clearlistmenu.cpp @@ -0,0 +1,34 @@ +#include "clearlistmenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/clearlistmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace ClearListMenu { + +void Configure() { + BackgroundSprite = EnsureGetMember("BackgroundSprite"); + GetMemberArray(std::span(TubeSprites, EndingCount), "TubeSprites"); + GetMemberArray(std::span(TubePositions, EndingCount), + "TubePositions"); + GetMemberArray(std::span(LabelSprites, EndingCount), "LabelSprites"); + GetMemberArray(std::span(LabelPositions, EndingCount), + "LabelPositions"); + GetMemberArray(std::span(EndingFlags, EndingCount), "EndingFlags"); + GuideSprite = EnsureGetMember("GuideSprite"); + GuidePos = EnsureGetMember("GuidePos"); + CountMax = EnsureGetMember("CountMax"); + + UI::ClearListMenuPtr = new UI::SG0::ClearListMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::ClearListMenuPtr); +} + +} // namespace ClearListMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/clearlistmenu.h b/src/profile/games/sg0/clearlistmenu.h new file mode 100644 index 000000000..3b28c47c4 --- /dev/null +++ b/src/profile/games/sg0/clearlistmenu.h @@ -0,0 +1,29 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace ClearListMenu { + +int constexpr EndingCount = 6; + +void Configure(); + +inline Sprite BackgroundSprite; +// Per tube, left to right: the lit character tube, its draw position, the +// ending title under it and the clear flag that lights it. +inline Sprite TubeSprites[EndingCount]; +inline glm::vec2 TubePositions[EndingCount]; +inline Sprite LabelSprites[EndingCount]; +inline glm::vec2 LabelPositions[EndingCount]; +inline int EndingFlags[EndingCount]; +inline Sprite GuideSprite; +inline glm::vec2 GuidePos; +inline int CountMax; + +} // namespace ClearListMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/helpmenu.cpp b/src/profile/games/sg0/helpmenu.cpp new file mode 100644 index 000000000..f8f0bf3c5 --- /dev/null +++ b/src/profile/games/sg0/helpmenu.cpp @@ -0,0 +1,25 @@ +#include "helpmenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/helpmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace HelpMenu { + +void Configure() { + PageSprites = EnsureGetMember>("PageSprites"); + CountMax = EnsureGetMember("CountMax"); + + UI::HelpMenuPtr = new UI::SG0::HelpMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::HelpMenuPtr); +} + +} // namespace HelpMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/helpmenu.h b/src/profile/games/sg0/helpmenu.h new file mode 100644 index 000000000..2d0cd8197 --- /dev/null +++ b/src/profile/games/sg0/helpmenu.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace HelpMenu { + +void Configure(); + +inline std::vector PageSprites; +// SW_SYSSUBMENUCT value at which the help is fully shown. +inline int CountMax; + +} // namespace HelpMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/librarymenu.cpp b/src/profile/games/sg0/librarymenu.cpp new file mode 100644 index 000000000..55a44aa42 --- /dev/null +++ b/src/profile/games/sg0/librarymenu.cpp @@ -0,0 +1,96 @@ +#include "librarymenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/librarymenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace LibraryMenu { + +void Configure() { + BackgroundSprite = EnsureGetMember("BackgroundSprite"); + GetMemberArray(std::span(TabSprites, TabCount), "TabSprites"); + GetMemberArray(std::span(TabSelectedSprites, TabCount), + "TabSelectedSprites"); + TabPos = EnsureGetMember("TabPos"); + TabPitch = EnsureGetMember("TabPitch"); + GetMemberArray(std::span(TitleSprites, LibraryCount), "TitleSprites"); + TitlePos = EnsureGetMember("TitlePos"); + GetMemberArray(std::span(GuideSprites, GuideCount), "GuideSprites"); + GuideRight = EnsureGetMember("GuideRight"); + GuideY = EnsureGetMember("GuideY"); + + ThumbnailSprites = EnsureGetMember>("ThumbnailSprites"); + NoDataSprite = EnsureGetMember("NoDataSprite"); + NoDataSelectedSprite = EnsureGetMember("NoDataSelectedSprite"); + SelectedFrameSprite = EnsureGetMember("SelectedFrameSprite"); + SelectedGlowSprite = EnsureGetMember("SelectedGlowSprite"); + CgGridPos = EnsureGetMember("CgGridPos"); + CgCellPitch = EnsureGetMember("CgCellPitch"); + CgColumns = EnsureGetMember("CgColumns"); + CgRows = EnsureGetMember("CgRows"); + ThumbnailOffset = EnsureGetMember("ThumbnailOffset"); + ScrollThumbSprite = EnsureGetMember("ScrollThumbSprite"); + ScrollX = EnsureGetMember("ScrollX"); + ScrollTop = EnsureGetMember("ScrollTop"); + ScrollBottom = EnsureGetMember("ScrollBottom"); + AllClearCgs = EnsureGetMember>("AllClearCgs"); + EndingFlags = EnsureGetMember>("EndingFlags"); + CgLoadBuffer = EnsureGetMember("CgLoadBuffer"); + CgZoomMax = EnsureGetMember("CgZoomMax"); + CgScrollSpeed = EnsureGetMember("CgScrollSpeed"); + + PlayerSprite = EnsureGetMember("PlayerSprite"); + PlayerPos = EnsureGetMember("PlayerPos"); + ListHeaderSprite = EnsureGetMember("ListHeaderSprite"); + GetMemberArray(std::span(ListRowSprites, 2), "ListRowSprites"); + ListSelectedSprite = EnsureGetMember("ListSelectedSprite"); + ListBounds = EnsureGetMember("ListBounds"); + ListRowHeight = EnsureGetMember("ListRowHeight"); + ListVisibleRows = EnsureGetMember("ListVisibleRows"); + ListTrackX = EnsureGetMember("ListTrackX"); + ListTitleX = EnsureGetMember("ListTitleX"); + ListArtistX = EnsureGetMember("ListArtistX"); + ListFontSize = EnsureGetMember("ListFontSize"); + NowPlayingPos = EnsureGetMember("NowPlayingPos"); + NowPlayingFontSize = EnsureGetMember("NowPlayingFontSize"); + GetMemberArray(std::span(RepeatModeSprites, 4), "RepeatModeSprites"); + RepeatModePos = EnsureGetMember("RepeatModePos"); + PlayIconSprite = EnsureGetMember("PlayIconSprite"); + TrackStringBase = EnsureGetMember("TrackStringBase"); + LockedTrackString = EnsureGetMember("LockedTrackString"); + TrackBgmIds = EnsureGetMember>("TrackBgmIds"); + TrackBgmAltIds = EnsureGetMember>("TrackBgmAltIds"); + TrackMovieUnlocks = EnsureGetMember>("TrackMovieUnlocks"); + + MovieThumbnailSprites = + EnsureGetMember>("MovieThumbnailSprites"); + MovieIds = EnsureGetMember>("MovieIds"); + MovieGridPos = EnsureGetMember("MovieGridPos"); + MovieCellPitch = EnsureGetMember("MovieCellPitch"); + MovieColumns = EnsureGetMember("MovieColumns"); + MovieThumbnailScale = EnsureGetMember("MovieThumbnailScale"); + MovieTitleOffsetY = EnsureGetMember("MovieTitleOffsetY"); + MovieStringBase = EnsureGetMember("MovieStringBase"); + MovieFontSize = EnsureGetMember("MovieFontSize"); + MovieUnlockLines = + EnsureGetMember>>("MovieUnlockLines"); + + TextColor = EnsureGetMember("TextColor"); + LockedTextColor = EnsureGetMember("LockedTextColor"); + SelectedTextColor = EnsureGetMember("SelectedTextColor"); + SystemScriptBuffer = EnsureGetMember("SystemScriptBuffer"); + CountMax = EnsureGetMember("CountMax"); + + UI::LibraryMenuPtr = new UI::SG0::LibraryMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::LibraryMenuPtr); +} + +} // namespace LibraryMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/librarymenu.h b/src/profile/games/sg0/librarymenu.h new file mode 100644 index 000000000..89ac4062c --- /dev/null +++ b/src/profile/games/sg0/librarymenu.h @@ -0,0 +1,101 @@ +#pragma once + +#include + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace LibraryMenu { + +int constexpr TabCount = 4; // CLEAR LIST, CG, SOUND, MOVIE +int constexpr LibraryCount = 3; // CG, SOUND, MOVIE +int constexpr GuideCount = 4; // CG list, CG viewer, sound, movie + +void Configure(); + +inline Sprite BackgroundSprite; +inline Sprite TabSprites[TabCount]; +inline Sprite TabSelectedSprites[TabCount]; +inline glm::vec2 TabPos; +inline float TabPitch; +inline Sprite TitleSprites[LibraryCount]; +inline glm::vec2 TitlePos; +inline Sprite GuideSprites[GuideCount]; +// Guides are right-aligned to this x, at this y. +inline float GuideRight; +inline float GuideY; + +// CG library: one thumbnail per Profile::SaveSystem::AlbumEvData entry. +inline std::vector ThumbnailSprites; +inline Sprite NoDataSprite; +inline Sprite NoDataSelectedSprite; +inline Sprite SelectedFrameSprite; +inline Sprite SelectedGlowSprite; +inline glm::vec2 CgGridPos; +inline glm::vec2 CgCellPitch; +inline int CgColumns; +inline int CgRows; +inline glm::vec2 ThumbnailOffset; +inline Sprite ScrollThumbSprite; +inline float ScrollX; +inline float ScrollTop; +inline float ScrollBottom; +// Thumbnails without an EV flag, unlocked once all six endings are cleared. +inline std::vector AllClearCgs; +inline std::vector EndingFlags; +inline int CgLoadBuffer; +inline float CgZoomMax; +inline float CgScrollSpeed; + +// Sound library: the player window and its track list. +inline Sprite PlayerSprite; +inline glm::vec2 PlayerPos; +inline Sprite ListHeaderSprite; +inline Sprite ListRowSprites[2]; +inline Sprite ListSelectedSprite; +inline RectF ListBounds; +inline float ListRowHeight; +inline int ListVisibleRows; +inline float ListTrackX; +inline float ListTitleX; +inline float ListArtistX; +inline float ListFontSize; +inline glm::vec2 NowPlayingPos; +inline float NowPlayingFontSize; +inline Sprite RepeatModeSprites[4]; +inline glm::vec2 RepeatModePos; +inline Sprite PlayIconSprite; +inline int TrackStringBase; +inline int LockedTrackString; +// Per track: bgm.mpk ids (loop, then no-loop variant or -1) and, for the +// vocal songs, the movie whose viewing also unlocks them (-1 for none). +inline std::vector TrackBgmIds; +inline std::vector TrackBgmAltIds; +inline std::vector TrackMovieUnlocks; + +// Movie library. +inline std::vector MovieThumbnailSprites; +inline std::vector MovieIds; +inline glm::vec2 MovieGridPos; +inline glm::vec2 MovieCellPitch; +inline int MovieColumns; +inline float MovieThumbnailScale; +inline float MovieTitleOffsetY; +inline int MovieStringBase; +inline float MovieFontSize; +// Per movie: (script id, line id) pairs, the line read just before the +// movie plays; the movie is unlocked once any of them has been read. +inline std::vector> MovieUnlockLines; + +inline uint32_t TextColor; +inline uint32_t LockedTextColor; +inline uint32_t SelectedTextColor; +inline int SystemScriptBuffer; +inline int CountMax; + +} // namespace LibraryMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/optionsmenu.cpp b/src/profile/games/sg0/optionsmenu.cpp new file mode 100644 index 000000000..8ec49e30a --- /dev/null +++ b/src/profile/games/sg0/optionsmenu.cpp @@ -0,0 +1,81 @@ +#include "optionsmenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/optionsmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace OptionsMenu { + +void Configure() { + BackgroundSprite = EnsureGetMember("BackgroundSprite"); + GuideSprite = EnsureGetMember("GuideSprite"); + GuidePos = EnsureGetMember("GuidePos"); + VoiceGuideSprite = EnsureGetMember("VoiceGuideSprite"); + VoiceGuidePos = EnsureGetMember("VoiceGuidePos"); + GetMemberArray(std::span(PageButtonSprites, PageCount), + "PageButtonSprites"); + GetMemberArray(std::span(PageButtonBounds, PageCount), + "PageButtonBounds"); + GetMemberArray(std::span(PageHeadingSprites, PageCount), + "PageHeadingSprites"); + HeadingPos = EnsureGetMember("HeadingPos"); + + ContentX = EnsureGetMember("ContentX"); + RowsTop = EnsureGetMember("RowsTop"); + RowHeight = EnsureGetMember("RowHeight"); + VoiceRowHeight = EnsureGetMember("VoiceRowHeight"); + RowPadding = EnsureGetMember("RowPadding"); + RowWidth = EnsureGetMember("RowWidth"); + + SliderFrameSprite = EnsureGetMember("SliderFrameSprite"); + SliderFillSprite = EnsureGetMember("SliderFillSprite"); + SliderKnobSprite = EnsureGetMember("SliderKnobSprite"); + SliderOffset = EnsureGetMember("SliderOffset"); + SliderFillOffset = EnsureGetMember("SliderFillOffset"); + SliderKnobOffsetY = EnsureGetMember("SliderKnobOffsetY"); + VoiceSliderScale = EnsureGetMember("VoiceSliderScale"); + VoiceSliderOffsetY = EnsureGetMember("VoiceSliderOffsetY"); + VoiceOffSprite = EnsureGetMember("VoiceOffSprite"); + + const uint32_t color = EnsureGetMember("HighlightColor"); + HighlightColor = + glm::vec3((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF) / + 255.0f; + HighlightOpacity = EnsureGetMember("HighlightOpacity"); + UnselectedBrightness = EnsureGetMember("UnselectedBrightness"); + CountMax = EnsureGetMember("CountMax"); + + Rows.clear(); + EnsurePushMemberOfType("Rows", LUA_TTABLE); + ForEachProfileArray([](uint32_t index) { + Row row; + row.Page = EnsureGetMember("Page"); + const std::string setting = EnsureGetMember("Setting"); + const auto kind = magic_enum::enum_cast(setting); + if (!kind) { + throw std::runtime_error( + fmt::format("Unknown option setting {}", setting)); + } + row.Kind = *kind; + row.Index = TryGetMember("Index").value_or(0); + row.Label = EnsureGetMember("Label"); + row.Choices = TryGetMember>("Choices").value_or( + std::vector{}); + if (Rows.size() <= index) Rows.resize(index + 1); + Rows[index] = std::move(row); + }); + Pop(); + + UI::OptionsMenuPtr = new UI::SG0::OptionsMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::OptionsMenuPtr); +} + +} // namespace OptionsMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/optionsmenu.h b/src/profile/games/sg0/optionsmenu.h new file mode 100644 index 000000000..03238847f --- /dev/null +++ b/src/profile/games/sg0/optionsmenu.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace OptionsMenu { + +void Configure(); + +int constexpr PageCount = 4; // BASIC, TEXT, SOUND, VOICE +int constexpr VoicePage = 3; + +// What a row edits. Choice rows list their choice words in the profile; +// the others are MIN-MAX sliders. +enum class Setting : int { + FullScreen, + Resolution, + TriggerStopSkip, + ShowTipsNotification, + AutoQuickSave, + TextSpeed, + AutoSpeed, + SkipRead, + VoiceVolume, + MusicVolume, + SEVolume, + MovieVolume, + SyncVoice, + SkipVoice, + CharacterVoice, // per-character volume and mute (Index = voice id) +}; + +struct Row { + int Page = 0; + Setting Kind = Setting::TextSpeed; + int Index = 0; + Sprite Label; + std::vector Choices; +}; + +inline std::vector Rows; + +inline Sprite BackgroundSprite; +inline Sprite GuideSprite; +inline glm::vec2 GuidePos; +inline Sprite VoiceGuideSprite; +inline glm::vec2 VoiceGuidePos; +inline Sprite PageButtonSprites[PageCount]; // highlighted page buttons +inline RectF PageButtonBounds[PageCount]; +inline Sprite PageHeadingSprites[PageCount]; +inline glm::vec2 HeadingPos; + +inline float ContentX; +inline float RowsTop; +inline float RowHeight; +inline float VoiceRowHeight; +inline float RowPadding; +inline float RowWidth; + +inline Sprite SliderFrameSprite; +inline Sprite SliderFillSprite; +inline Sprite SliderKnobSprite; +inline glm::vec2 SliderOffset; // frame, relative to the row's label +inline glm::vec2 SliderFillOffset; // fill, relative to the frame +inline float SliderKnobOffsetY; +inline float VoiceSliderScale; // the VOICE page's 16 rows are tighter +inline float VoiceSliderOffsetY; +inline Sprite VoiceOffSprite; + +inline glm::vec3 HighlightColor; +inline float HighlightOpacity; +inline float UnselectedBrightness; + +// SW_SYSSUBMENUCT value at which the screen is fully shown. +inline int CountMax; + +} // namespace OptionsMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/phone.cpp b/src/profile/games/sg0/phone.cpp new file mode 100644 index 000000000..afe6dfb7f --- /dev/null +++ b/src/profile/games/sg0/phone.cpp @@ -0,0 +1,66 @@ +#include "phone.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/phone.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace Phone { + +void Configure() { + if (!TryPushMember("Phone")) return; + AssertIs(LUA_TTABLE); + + BodySprite = EnsureGetMember("BodySprite"); + Scale = EnsureGetMember("Scale"); + ShownPos = EnsureGetMember("ShownPos"); + HiddenX = EnsureGetMember("HiddenX"); + ScreenOffset = EnsureGetMember("ScreenOffset"); + ScreenSize = EnsureGetMember("ScreenSize"); + WallpaperSprite = EnsureGetMember("WallpaperSprite"); + ScreenShadeAlpha = EnsureGetMember("ScreenShadeAlpha"); + SelectionSprite = EnsureGetMember("SelectionSprite"); + Padding = EnsureGetMember("Padding"); + ItemsTop = EnsureGetMember("ItemsTop"); + RowHeight = EnsureGetMember("RowHeight"); + RowPadding = EnsureGetMember("RowPadding"); + TitleFontSize = EnsureGetMember("TitleFontSize"); + ItemFontSize = EnsureGetMember("ItemFontSize"); + TextColor = EnsureGetMember("TextColor"); + OutlineColor = EnsureGetMember("OutlineColor"); + + RineAvatarSprite = EnsureGetMember("RineAvatarSprite"); + RineAvatarStepY = EnsureGetMember("RineAvatarStepY"); + RineStickerSprite = EnsureGetMember("RineStickerSprite"); + RineStickerStep = EnsureGetMember("RineStickerStep"); + RineStickerColumns = EnsureGetMember("RineStickerColumns"); + RineTop = EnsureGetMember("RineTop"); + RineMargin = EnsureGetMember("RineMargin"); + RineGap = EnsureGetMember("RineGap"); + RineAvatarSize = EnsureGetMember("RineAvatarSize"); + RineStickerSize = EnsureGetMember("RineStickerSize"); + RineFontSize = EnsureGetMember("RineFontSize"); + RineLineHeight = EnsureGetMember("RineLineHeight"); + RineBubbleMaxWidth = EnsureGetMember("RineBubbleMaxWidth"); + RineBubblePadding = EnsureGetMember("RineBubblePadding"); + RineOwnColor = EnsureGetMember("RineOwnColor"); + RineOtherColor = EnsureGetMember("RineOtherColor"); + RineTextColor = EnsureGetMember("RineTextColor"); + RineReplyRowHeight = EnsureGetMember("RineReplyRowHeight"); + RineReplyStickerSize = EnsureGetMember("RineReplyStickerSize"); + RineInputHeight = EnsureGetMember("RineInputHeight"); + + UI::SG0::PhonePtr = new UI::SG0::Phone(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::SG0::PhonePtr); + + Pop(); +} + +} // namespace Phone +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/phone.h b/src/profile/games/sg0/phone.h new file mode 100644 index 000000000..03c5005b8 --- /dev/null +++ b/src/profile/games/sg0/phone.h @@ -0,0 +1,59 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace Phone { + +void Configure(); + +// Phone body (phone_b.dds), drawn at Scale; positions are design pixels. +inline Sprite BodySprite; +inline float Scale; +inline glm::vec2 ShownPos; +inline float HiddenX; +inline glm::vec2 ScreenOffset; // screen rectangle relative to the body +inline glm::vec2 ScreenSize; + +inline Sprite WallpaperSprite; +inline float ScreenShadeAlpha; +inline Sprite SelectionSprite; + +// Text inside the screen, relative to the screen's top left. +inline glm::vec2 Padding; +inline float ItemsTop; +inline float RowHeight; +inline float RowPadding; +inline float TitleFontSize; +inline float ItemFontSize; +inline uint32_t TextColor; +inline uint32_t OutlineColor; + +// RINE screen (phone_rine.dds avatars and stickers), relative to the screen. +inline Sprite RineAvatarSprite; // avatar 0; avatar n is RineAvatarStepY lower +inline float RineAvatarStepY; +inline Sprite RineStickerSprite; // sticker 0 of a RineStickerColumns-wide grid +inline glm::vec2 RineStickerStep; +inline int RineStickerColumns; +inline float RineTop; // message area top (below the title) +inline float RineMargin; +inline float RineGap; +inline float RineAvatarSize; +inline glm::vec2 RineStickerSize; +inline float RineFontSize; +inline float RineLineHeight; +inline float RineBubbleMaxWidth; +inline glm::vec2 RineBubblePadding; +inline uint32_t RineOwnColor; +inline uint32_t RineOtherColor; +inline uint32_t RineTextColor; +inline float RineReplyRowHeight; +inline glm::vec2 RineReplyStickerSize; +inline float RineInputHeight; + +} // namespace Phone +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/savemenu.cpp b/src/profile/games/sg0/savemenu.cpp new file mode 100644 index 000000000..2c371e15a --- /dev/null +++ b/src/profile/games/sg0/savemenu.cpp @@ -0,0 +1,51 @@ +#include "savemenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/savemenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace SaveMenu { + +void Configure() { + BackgroundSprite = EnsureGetMember("BackgroundSprite"); + PanelSprite = EnsureGetMember("PanelSprite"); + PanelPos = EnsureGetMember("PanelPos"); + GetMemberArray(std::span(HeaderSprites, 3), "HeaderSprites"); + HeaderPos = EnsureGetMember("HeaderPos"); + ButtonGuideSprite = EnsureGetMember("ButtonGuideSprite"); + ButtonGuidePos = EnsureGetMember("ButtonGuidePos"); + + NoDataSprite = EnsureGetMember("NoDataSprite"); + EntryFrameSprite = EnsureGetMember("EntryFrameSprite"); + PlayTimeLabelSprite = EnsureGetMember("PlayTimeLabelSprite"); + SaveDateLabelSprite = EnsureGetMember("SaveDateLabelSprite"); + LockedSprite = EnsureGetMember("LockedSprite"); + GetMemberArray(std::span(PageDigitSprites, 7), "PageDigitSprites"); + + EntryStart = EnsureGetMember("EntryStart"); + EntrySpacing = EnsureGetMember("EntrySpacing"); + FrameOffset = EnsureGetMember("FrameOffset"); + ThumbnailOffset = EnsureGetMember("ThumbnailOffset"); + ThumbnailSize = EnsureGetMember("ThumbnailSize"); + PlayTimeLabelOffset = EnsureGetMember("PlayTimeLabelOffset"); + SaveDateLabelOffset = EnsureGetMember("SaveDateLabelOffset"); + PlayTimeTextOffset = EnsureGetMember("PlayTimeTextOffset"); + SaveDateTextOffset = EnsureGetMember("SaveDateTextOffset"); + LockedOffset = EnsureGetMember("LockedOffset"); + PageNumberPos = EnsureGetMember("PageNumberPos"); + TextFontSize = EnsureGetMember("TextFontSize"); + SubMenuCountMax = EnsureGetMember("SubMenuCountMax"); + + UI::SaveMenuPtr = new UI::SG0::SaveMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::SaveMenuPtr); +} + +} // namespace SaveMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/savemenu.h b/src/profile/games/sg0/savemenu.h new file mode 100644 index 000000000..924afc1d8 --- /dev/null +++ b/src/profile/games/sg0/savemenu.h @@ -0,0 +1,54 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace SaveMenu { + +void Configure(); + +// 48 save slots: 6 pages of 2 columns x 4 rows. +int constexpr Pages = 6; +int constexpr RowsPerPage = 4; +int constexpr EntriesPerRow = 2; +int constexpr EntriesPerPage = RowsPerPage * EntriesPerRow; + +inline Sprite BackgroundSprite; +inline Sprite PanelSprite; +inline glm::vec2 PanelPos; +// Indexed by UI::SaveMenuPageType: QuickLoad, Save, Load. +inline Sprite HeaderSprites[3]; +inline glm::vec2 HeaderPos; +inline Sprite ButtonGuideSprite; +inline glm::vec2 ButtonGuidePos; + +inline Sprite NoDataSprite; +inline Sprite EntryFrameSprite; +inline Sprite PlayTimeLabelSprite; +inline Sprite SaveDateLabelSprite; +inline Sprite LockedSprite; +inline Sprite PageDigitSprites[7]; // "/" then 1-6 + +// Entry geometry, relative to the panel / entry origin. +inline glm::vec2 EntryStart; +inline glm::vec2 EntrySpacing; +inline glm::vec2 FrameOffset; +inline glm::vec2 ThumbnailOffset; +inline glm::vec2 ThumbnailSize; +inline glm::vec2 PlayTimeLabelOffset; +inline glm::vec2 SaveDateLabelOffset; +inline glm::vec2 PlayTimeTextOffset; +inline glm::vec2 SaveDateTextOffset; +inline glm::vec2 LockedOffset; +inline glm::vec2 PageNumberPos; +inline float TextFontSize; + +// The SG0 scripts count SW_SYSSUBMENUCT up to this value while opening. +inline int SubMenuCountMax; + +} // namespace SaveMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/sysmesbox.cpp b/src/profile/games/sg0/sysmesbox.cpp new file mode 100644 index 000000000..b0e26db5c --- /dev/null +++ b/src/profile/games/sg0/sysmesbox.cpp @@ -0,0 +1,33 @@ +#include "sysmesbox.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/sysmesbox.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace SysMesBox { + +void Configure() { + BackgroundSprite = EnsureGetMember("BackgroundSprite"); + BackgroundOpacity = EnsureGetMember("BackgroundOpacity"); + MessageColor = EnsureGetMember("MessageColor"); + ChoiceColor = EnsureGetMember("ChoiceColor"); + ChoiceHighlightColor = EnsureGetMember("ChoiceHighlightColor"); + OutlineColor = EnsureGetMember("OutlineColor"); + ChoiceSpacing = EnsureGetMember("ChoiceSpacing"); + ChoicePadding = EnsureGetMember("ChoicePadding"); + + UI::SysMesBox::DrawType = + EnsureGetMember("DrawType"); + UI::SysMesBox::Factory = []() -> UI::SysMesBox* { + return new UI::SG0::SysMesBox(); + }; +} + +} // namespace SysMesBox +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/sysmesbox.h b/src/profile/games/sg0/sysmesbox.h new file mode 100644 index 000000000..4af963299 --- /dev/null +++ b/src/profile/games/sg0/sysmesbox.h @@ -0,0 +1,25 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace SysMesBox { + +void Configure(); + +inline Sprite BackgroundSprite; +inline float BackgroundOpacity; + +inline uint32_t MessageColor; +inline uint32_t ChoiceColor; +inline uint32_t ChoiceHighlightColor; +inline uint32_t OutlineColor; +inline float ChoiceSpacing; +inline glm::vec2 ChoicePadding; + +} // namespace SysMesBox +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/systemmenu.cpp b/src/profile/games/sg0/systemmenu.cpp new file mode 100644 index 000000000..9a2eb2e7c --- /dev/null +++ b/src/profile/games/sg0/systemmenu.cpp @@ -0,0 +1,36 @@ +#include "systemmenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/systemmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace SystemMenu { + +void Configure() { + BackgroundSprite = EnsureGetMember("BackgroundSprite"); + MonitorScreen = EnsureGetMember("MonitorScreen"); + GlassSprite = EnsureGetMember("GlassSprite"); + GlassOpacity = EnsureGetMember("GlassOpacity"); + RingSprite = EnsureGetMember("RingSprite"); + RingPos = EnsureGetMember("RingPos"); + GetMemberArray( + std::span(EntryCenters, Profile::SystemMenu::MenuEntriesNum), + "EntryCenters"); + NavOrder = EnsureGetMember>("NavOrder"); + FocusScale = EnsureGetMember("FocusScale"); + UnfocusedBrightness = EnsureGetMember("UnfocusedBrightness"); + CountMax = EnsureGetMember("CountMax"); + + UI::SystemMenuPtr = new UI::SG0::SystemMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::SystemMenuPtr); +} + +} // namespace SystemMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/systemmenu.h b/src/profile/games/sg0/systemmenu.h new file mode 100644 index 000000000..7b67203b2 --- /dev/null +++ b/src/profile/games/sg0/systemmenu.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "../../../spritesheet.h" +#include "../../ui/systemmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace SystemMenu { + +void Configure(); + +inline Sprite BackgroundSprite; // monitor frame +inline RectF MonitorScreen; // where the screen capture goes +inline Sprite GlassSprite; +inline float GlassOpacity; +inline Sprite RingSprite; +inline glm::vec2 RingPos; + +// Centre of each button, indexed by menu id (the script's jump-table index). +inline glm::vec2 EntryCenters[Profile::SystemMenu::MenuEntriesNumMax]; +// Button ids in cursor order. +inline std::vector NavOrder; +inline float FocusScale; +inline float UnfocusedBrightness; + +// SW_SYSMENUCT value at which the menu is fully open. +inline int CountMax; + +} // namespace SystemMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/tipsmenu.cpp b/src/profile/games/sg0/tipsmenu.cpp new file mode 100644 index 000000000..0f7f750df --- /dev/null +++ b/src/profile/games/sg0/tipsmenu.cpp @@ -0,0 +1,65 @@ +#include "tipsmenu.h" +#include "../../profile_internal.h" + +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/tipsmenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace TipsMenu { + +void Configure() { + PanelsSprite = EnsureGetMember("PanelsSprite"); + HeaderSprite = EnsureGetMember("HeaderSprite"); + HeaderPos = EnsureGetMember("HeaderPos"); + HeaderIconSprite = EnsureGetMember("HeaderIconSprite"); + HeaderIconPos = EnsureGetMember("HeaderIconPos"); + GuideSprite = EnsureGetMember("GuideSprite"); + GuidePos = EnsureGetMember("GuidePos"); + + GetMemberArray(std::span(TabSprites, TabCount), "TabSprites"); + GetMemberArray(std::span(TabDisabledSprites, TabCount), + "TabDisabledSprites"); + GetMemberArray(std::span(TabSelectedSprites, TabCount), + "TabSelectedSprites"); + GetMemberArray(std::span(TabBounds, TabCount), "TabBounds"); + + ListBounds = EnsureGetMember("ListBounds"); + ListTop = EnsureGetMember("ListTop"); + RowHeight = EnsureGetMember("RowHeight"); + RowPadding = EnsureGetMember("RowPadding"); + VisibleRows = EnsureGetMember("VisibleRows"); + ListNumberX = EnsureGetMember("ListNumberX"); + ListNameX = EnsureGetMember("ListNameX"); + ListNameMaxWidth = EnsureGetMember("ListNameMaxWidth"); + ListFontSize = EnsureGetMember("ListFontSize"); + SelectionSprite = EnsureGetMember("SelectionSprite"); + NewBadgeSprite = EnsureGetMember("NewBadgeSprite"); + NewBadgeX = EnsureGetMember("NewBadgeX"); + + NumberPos = EnsureGetMember("NumberPos"); + CategoryPos = EnsureGetMember("CategoryPos"); + NamePos = EnsureGetMember("NamePos"); + NameMaxWidth = EnsureGetMember("NameMaxWidth"); + HeaderFontSize = EnsureGetMember("HeaderFontSize"); + NameFontSize = EnsureGetMember("NameFontSize"); + NoteBounds = EnsureGetMember("NoteBounds"); + NoteThumbSprite = EnsureGetMember("NoteThumbSprite"); + NoteThumbX = EnsureGetMember("NoteThumbX"); + NoteScrollSpeed = EnsureGetMember("NoteScrollSpeed"); + + TextColor = EnsureGetMember("TextColor"); + LockedColor = EnsureGetMember("LockedColor"); + CountMax = EnsureGetMember("CountMax"); + + UI::TipsMenuPtr = new UI::SG0::TipsMenu(); + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::TipsMenuPtr); +} + +} // namespace TipsMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/tipsmenu.h b/src/profile/games/sg0/tipsmenu.h new file mode 100644 index 000000000..aa9ca0461 --- /dev/null +++ b/src/profile/games/sg0/tipsmenu.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace TipsMenu { + +void Configure(); + +int constexpr TabCount = 4; // ALL, ACQUIRED, UNREAD, NEW + +inline Sprite PanelsSprite; // tipschips list + note panels, full screen +inline Sprite HeaderSprite; +inline glm::vec2 HeaderPos; +inline Sprite HeaderIconSprite; +inline glm::vec2 HeaderIconPos; +inline Sprite GuideSprite; +inline glm::vec2 GuidePos; + +inline Sprite TabSprites[TabCount]; +inline Sprite TabDisabledSprites[TabCount]; +inline Sprite TabSelectedSprites[TabCount]; +inline RectF TabBounds[TabCount]; + +// Tip list (left panel). +inline RectF ListBounds; +inline float ListTop; +inline float RowHeight; +inline float RowPadding; +inline int VisibleRows; +inline float ListNumberX; +inline float ListNameX; +inline float ListNameMaxWidth; +inline float ListFontSize; +inline Sprite SelectionSprite; +inline Sprite NewBadgeSprite; +inline float NewBadgeX; + +// Tip view (right panel). The note text itself is laid out by the scripts' +// text mode 7; NoteBounds clips and scrolls it. +inline glm::vec2 NumberPos; +inline glm::vec2 CategoryPos; +inline glm::vec2 NamePos; +inline float NameMaxWidth; +inline float HeaderFontSize; +inline float NameFontSize; +inline RectF NoteBounds; +inline Sprite NoteThumbSprite; +inline float NoteThumbX; +inline float NoteScrollSpeed; + +inline uint32_t TextColor; +inline uint32_t LockedColor; +// SW_SYSSUBMENUCT value at which the screen is fully shown. +inline int CountMax; + +} // namespace TipsMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/titlemenu.cpp b/src/profile/games/sg0/titlemenu.cpp new file mode 100644 index 000000000..ea915a0db --- /dev/null +++ b/src/profile/games/sg0/titlemenu.cpp @@ -0,0 +1,47 @@ +#include "titlemenu.h" +#include "../../profile_internal.h" + +#include "../../ui/titlemenu.h" +#include "../../../game.h" +#include "../../../ui/ui.h" +#include "../../../games/sg0/titlemenu.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace TitleMenu { + +void Configure() { + LogoSprite = EnsureGetMember("LogoSprite"); + LogoPos = EnsureGetMember("LogoPos"); + + ItemPos = EnsureGetMember("ItemPos"); + ItemSpacing = EnsureGetMember("ItemSpacing"); + GetMemberArray(std::span(SubItemSprites, SubItemsNum), + "SubItemSprites"); + GetMemberArray(std::span(SubItemHSprites, SubItemsNum), + "SubItemHighlightedSprites"); + SubItemOffsetX = EnsureGetMember("SubItemOffsetX"); + SubItemSpacing = EnsureGetMember("SubItemSpacing"); + + PressToStartAnimFastDurationIn = + EnsureGetMember("PressToStartAnimFastDurationIn"); + PressToStartAnimFastDurationOut = + EnsureGetMember("PressToStartAnimFastDurationOut"); + + UI::SG0::TitleMenu* menu = new UI::SG0::TitleMenu(); + menu->PressToStartAnimation.DurationIn = + Profile::TitleMenu::PressToStartAnimDurationIn; + menu->PressToStartAnimation.DurationOut = + Profile::TitleMenu::PressToStartAnimDurationOut; + menu->PressToStartAnimation.LoopMode = AnimationLoopMode::ReverseDirection; + UI::TitleMenuPtr = menu; + + auto drawType = EnsureGetMember("DrawType"); + UI::Menus[drawType].push_back(UI::TitleMenuPtr); +} + +} // namespace TitleMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/games/sg0/titlemenu.h b/src/profile/games/sg0/titlemenu.h new file mode 100644 index 000000000..289263ae2 --- /dev/null +++ b/src/profile/games/sg0/titlemenu.h @@ -0,0 +1,33 @@ +#pragma once + +#include "../../../spritesheet.h" + +namespace Impacto { +namespace Profile { +namespace SG0 { +namespace TitleMenu { + +void Configure(); + +// Main items: NEW GAME, CONTINUE, EXTRA, CONFIG, HELP. +// Sub items: LOAD, QUICK LOAD (CONTINUE); CLEAR LIST, CG LIBRARY, +// SOUND LIBRARY, MOVIE LIBRARY, TIPS (EXTRA). +int constexpr SubItemsNum = 7; + +inline Sprite LogoSprite; +inline glm::vec2 LogoPos; + +inline glm::vec2 ItemPos; +inline float ItemSpacing; +inline Sprite SubItemSprites[SubItemsNum]; +inline Sprite SubItemHSprites[SubItemsNum]; +inline float SubItemOffsetX; +inline float SubItemSpacing; + +inline float PressToStartAnimFastDurationIn; +inline float PressToStartAnimFastDurationOut; + +} // namespace TitleMenu +} // namespace SG0 +} // namespace Profile +} // namespace Impacto diff --git a/src/profile/sprites.cpp b/src/profile/sprites.cpp index 97315d2f3..5d2542de8 100644 --- a/src/profile/sprites.cpp +++ b/src/profile/sprites.cpp @@ -4,6 +4,7 @@ #include "../log.h" #include "../renderer/renderer.h" #include "../texture/texture.h" +#include #include namespace Impacto { @@ -23,6 +24,42 @@ static Texture LoadTexture(Io::Stream* stream, std::string name) { return texture; } +// Some ports split one atlas across two textures at the platform's maximum +// texture size (e.g. STEINS;GATE 0 Steam font_a/font_b). Stack the +// continuation page under the first one and crop to the sheet's design height +// so sprite coordinates stay those of the original single atlas. +static Texture LoadStackedTexture(Io::Stream* firstStream, + Io::Stream* continuationStream, + std::string name, int firstHeight, + int height) { + Texture first = LoadTexture(firstStream, name); + Texture continuation = LoadTexture(continuationStream, name + " (continued)"); + + // Pages may end in padding: only the first firstHeight rows are used. + if (firstHeight <= 0 || firstHeight > first.Height) + firstHeight = first.Height; + const size_t firstPixels = static_cast(first.Width) * first.Height; + if (first.Width != continuation.Width || + first.Format != continuation.Format || firstPixels == 0 || + height <= firstHeight || height > firstHeight + continuation.Height) { + ImpLog(LogLevel::Error, LogChannel::Profile, + "Spritesheet {:s} pages cannot be stacked to height {:d}, using " + "the first page only\n", + name, height); + return first; + } + + const size_t bytesPerPixel = first.Buffer.size() / firstPixels; + const size_t rowBytes = static_cast(first.Width) * bytesPerPixel; + Texture stacked{}; + stacked.Init(first.Format, first.Width, height); + std::copy_n(first.Buffer.begin(), firstHeight * rowBytes, + stacked.Buffer.begin()); + std::copy_n(continuation.Buffer.begin(), (height - firstHeight) * rowBytes, + stacked.Buffer.begin() + firstHeight * rowBytes); + return stacked; +} + void LoadSpritesheets() { EnsurePushMemberOfType("SpriteSheets", LUA_TTABLE); @@ -49,8 +86,23 @@ void LoadSpritesheets() { Window->Shutdown(); } - futures.emplace_back(std::tuple( - name, std::async(std::launch::async, LoadTexture, stream, name))); + if (auto continuationPath = + TryGetMember("ContinuationPath")) { + Io::Stream* continuationStream; + if (continuationPath->Open(&continuationStream) != IoError_OK) { + ImpLog(LogLevel::Fatal, LogChannel::Profile, + "Could not open spritesheet {:s} continuation page\n", name); + Window->Shutdown(); + } + futures.emplace_back(std::tuple( + name, std::async(std::launch::async, LoadStackedTexture, stream, + continuationStream, name, + TryGetMember("FirstPageHeight").value_or(0), + static_cast(sheet.DesignHeight)))); + } else { + futures.emplace_back(std::tuple( + name, std::async(std::launch::async, LoadTexture, stream, name))); + } } Pop(); } diff --git a/src/profile/ui/backlogmenu.cpp b/src/profile/ui/backlogmenu.cpp index ffeaf859c..b71b0e3c8 100644 --- a/src/profile/ui/backlogmenu.cpp +++ b/src/profile/ui/backlogmenu.cpp @@ -9,6 +9,7 @@ #include "../games/mo6tw/backlogmenu.h" #include "../games/chlcc/backlogmenu.h" #include "../games/cc/backlogmenu.h" +#include "../games/sg0/backlogmenu.h" namespace Impacto { namespace Profile { @@ -70,6 +71,10 @@ void Configure() { CC::BacklogMenu::Configure(); break; + case BacklogMenuType::SG0: + SG0::BacklogMenu::Configure(); + break; + case BacklogMenuType::None: break; } diff --git a/src/profile/ui/backlogmenu.h b/src/profile/ui/backlogmenu.h index 1eb0cfb36..3bd2ddfbc 100644 --- a/src/profile/ui/backlogmenu.h +++ b/src/profile/ui/backlogmenu.h @@ -13,6 +13,7 @@ enum class BacklogMenuType : int { MO6TW, CHLCC, CC, + SG0, }; enum class EntryHighlightLocationType : int { None, diff --git a/src/profile/ui/extramenus.cpp b/src/profile/ui/extramenus.cpp index 5f54fab3b..164b368cf 100644 --- a/src/profile/ui/extramenus.cpp +++ b/src/profile/ui/extramenus.cpp @@ -12,6 +12,8 @@ #include "../games/chlcc/moviemenu.h" #include "../games/chlcc/musicmenu.h" #include "../games/chlcc/albummenu.h" +#include "../games/sg0/librarymenu.h" +#include "../games/sg0/clearlistmenu.h" namespace Impacto { namespace Profile { @@ -28,6 +30,8 @@ void Configure() { if (LibraryMenuType == LibraryMenuType::CCLCC) { CCLCC::LibraryMenu::Configure(); + } else if (LibraryMenuType == LibraryMenuType::SG0) { + SG0::LibraryMenu::Configure(); } Pop(); @@ -42,6 +46,8 @@ void Configure() { CCLCC::ClearListMenu::Configure(); } else if (ClearListType == ClearListMenuType::CHLCC) { CHLCC::ClearListMenu::Configure(); + } else if (ClearListType == ClearListMenuType::SG0) { + SG0::ClearListMenu::Configure(); } Pop(); diff --git a/src/profile/ui/gamespecific.cpp b/src/profile/ui/gamespecific.cpp index 1cb5de0c5..5006a35b4 100644 --- a/src/profile/ui/gamespecific.cpp +++ b/src/profile/ui/gamespecific.cpp @@ -6,6 +6,7 @@ #include "../games/cclcc/delusiontrigger.h" #include "../games/cclcc/yesnotrigger.h" #include "../games/cclcc/mapsystem.h" +#include "../games/sg0/phone.h" namespace Impacto { namespace Profile { @@ -55,6 +56,9 @@ void Configure() { break; case GameSpecificType::Dash: break; + case GameSpecificType::SG0: + SG0::Phone::Configure(); + break; case GameSpecificType::None: break; } diff --git a/src/profile/ui/helpmenu.cpp b/src/profile/ui/helpmenu.cpp index 00ccb8129..fefb3599d 100644 --- a/src/profile/ui/helpmenu.cpp +++ b/src/profile/ui/helpmenu.cpp @@ -1,6 +1,7 @@ #include "helpmenu.h" #include "../profile_internal.h" #include "../games/cclcc/helpmenu.h" +#include "../games/sg0/helpmenu.h" #include "../../ui/ui.h" #include "../../log.h" @@ -18,6 +19,8 @@ void Configure() { if (Type == HelpMenuType::CCLCC) { CCLCC::HelpMenu::Configure(); + } else if (Type == HelpMenuType::SG0) { + SG0::HelpMenu::Configure(); } Pop(); diff --git a/src/profile/ui/optionsmenu.cpp b/src/profile/ui/optionsmenu.cpp index cd81f1206..6b6f86581 100644 --- a/src/profile/ui/optionsmenu.cpp +++ b/src/profile/ui/optionsmenu.cpp @@ -4,6 +4,7 @@ #include "../games/mo8/optionsmenu.h" #include "../games/chlcc/optionsmenu.h" #include "../games/cclcc/optionsmenu.h" +#include "../games/sg0/optionsmenu.h" #include "../../ui/ui.h" #include "../../log.h" @@ -39,6 +40,10 @@ void Configure() { CCLCC::OptionsMenu::Configure(); break; + case OptionsMenuType::SG0: + SG0::OptionsMenu::Configure(); + break; + case OptionsMenuType::None: break; } diff --git a/src/profile/ui/savemenu.cpp b/src/profile/ui/savemenu.cpp index 517e4892a..4d591be9f 100644 --- a/src/profile/ui/savemenu.cpp +++ b/src/profile/ui/savemenu.cpp @@ -4,6 +4,7 @@ #include "../games/mo8/savemenu.h" #include "../games/chlcc/savemenu.h" #include "../games/cclcc/savemenu.h" +#include "../games/sg0/savemenu.h" #include "../../ui/ui.h" namespace Impacto { @@ -31,6 +32,8 @@ void Configure() { CCLCC::SaveMenu::Configure(); } else if (Type == SaveMenuType::MO8) { MO8::SaveMenu::Configure(); + } else if (Type == SaveMenuType::SG0) { + SG0::SaveMenu::Configure(); } Pop(); diff --git a/src/profile/ui/sysmesbox.cpp b/src/profile/ui/sysmesbox.cpp index 8463de3be..2dd769a9e 100644 --- a/src/profile/ui/sysmesbox.cpp +++ b/src/profile/ui/sysmesbox.cpp @@ -4,6 +4,7 @@ #include "../games/mo6tw/sysmesbox.h" #include "../games/darling/sysmesbox.h" #include "../games/cc/sysmesbox.h" +#include "../games/sg0/sysmesbox.h" #include "../profile_internal.h" @@ -29,6 +30,8 @@ void Configure() { Darling::SysMesBox::Configure(); } else if (Type == SysMesBoxType::CC) { CC::SysMesBox::Configure(); + } else if (Type == SysMesBoxType::SG0) { + SG0::SysMesBox::Configure(); } TextFontSize = EnsureGetMember("TextFontSize"); diff --git a/src/profile/ui/systemmenu.cpp b/src/profile/ui/systemmenu.cpp index bae2f98eb..af8749268 100644 --- a/src/profile/ui/systemmenu.cpp +++ b/src/profile/ui/systemmenu.cpp @@ -6,6 +6,7 @@ #include "../games/chlcc/systemmenu.h" #include "../games/mo8/systemmenu.h" #include "../games/cclcc/systemmenu.h" +#include "../games/sg0/systemmenu.h" #include "../../ui/ui.h" #include "../../log.h" #include "../../renderer/renderer.h" @@ -51,6 +52,8 @@ void Configure() { MO8::SystemMenu::Configure(); } else if (Type == SystemMenuType::CCLCC) { CCLCC::SystemMenu::Configure(); + } else if (Type == SystemMenuType::SG0) { + SG0::SystemMenu::Configure(); } Pop(); diff --git a/src/profile/ui/tipsmenu.cpp b/src/profile/ui/tipsmenu.cpp index 87c6f0ef8..78ab1facd 100644 --- a/src/profile/ui/tipsmenu.cpp +++ b/src/profile/ui/tipsmenu.cpp @@ -3,6 +3,7 @@ #include "../games/mo6tw/tipsmenu.h" #include "../games/chlcc/tipsmenu.h" #include "../games/cclcc/tipsmenu.h" +#include "../games/sg0/tipsmenu.h" #include "../../ui/ui.h" #include "../../log.h" @@ -31,6 +32,8 @@ void Configure() { CHLCC::TipsMenu::Configure(); } else if (Type == TipsMenuType::CCLCC) { CCLCC::TipsMenu::Configure(); + } else if (Type == TipsMenuType::SG0) { + SG0::TipsMenu::Configure(); } else { UI::TipsMenuPtr = new UI::TipsMenu(); } diff --git a/src/profile/ui/titlemenu.cpp b/src/profile/ui/titlemenu.cpp index 42881313b..3927d6ab0 100644 --- a/src/profile/ui/titlemenu.cpp +++ b/src/profile/ui/titlemenu.cpp @@ -8,6 +8,7 @@ #include "../games/mo8/titlemenu.h" #include "../games/cc/titlemenu.h" #include "../games/cclcc/titlemenu.h" +#include "../games/sg0/titlemenu.h" #include "../../log.h" namespace Impacto { @@ -53,6 +54,8 @@ void Configure() { CC::TitleMenu::Configure(); } else if (Type == TitleMenuType::CCLCC) { CCLCC::TitleMenu::Configure(); + } else if (Type == TitleMenuType::SG0) { + SG0::TitleMenu::Configure(); } // if (Implementation != 0) { diff --git a/src/scriptvars.h b/src/scriptvars.h index 2e7fd7fb7..9e74d78ef 100644 --- a/src/scriptvars.h +++ b/src/scriptvars.h @@ -113,6 +113,7 @@ V(SW_SYSMESANIMCTCUR) V(SW_SYSMESANIMCTF) V(SW_SINSTALL_ALL) V(SW_BG1SURF) +V(SW_CUTIN1) V(SW_MAP_PRI) V(SW_MAP_ALPHA) V(SW_DELUSION_OVERLAY_BUF) diff --git a/src/text/fonts/font.h b/src/text/fonts/font.h index dfee74217..c8a32a851 100644 --- a/src/text/fonts/font.h +++ b/src/text/fonts/font.h @@ -116,7 +116,16 @@ class SingleSheetFont : public Font { glm::vec2 CellSize; std::vector AdvanceWidths; + // Optional: where each glyph's ink starts in its cell. The glyph is then + // sampled from there, so narrow glyphs sit next to their neighbours. + std::vector LeftBearings; + public: + void SetLeftBearings(std::vector&& leftBearings) { + LeftBearings = std::move(leftBearings); + } + + protected: SingleSheetFont(FontType fontType, float bitmapEmWidth, float bitmapEmHeight, OpacityCurve foregroundOpacityCurve, OpacityCurve outlineOpacityCurve, SpriteSheet sheet, @@ -134,6 +143,10 @@ class SingleSheetFont : public Font { const uint16_t col = static_cast(id % GridSize.x); const float width = AdvanceWidths[id]; + if (id < LeftBearings.size()) { + return Sprite(Sheet, col * CellSize.x + LeftBearings[id], + row * CellSize.y + 1.0f, width, BitmapEmHeight - 2.0f); + } return Sprite(Sheet, col * CellSize.x + 1.0f, row * CellSize.y + 1.0f, width - 2.0f, BitmapEmHeight - 2.0f); } diff --git a/src/texture/ddsloader.cpp b/src/texture/ddsloader.cpp index 7810919e3..a0cd2ab57 100644 --- a/src/texture/ddsloader.cpp +++ b/src/texture/ddsloader.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md +#include #include #include "../log.h" @@ -200,6 +201,11 @@ bool internal_readimg(Stream* stream, uint8_t* dst, int w, int h, int d) { } bool TextureLoadDDS(Stream* stream, Texture* outTexture) { + // The decoder state above is file-global, and spritesheets are loaded + // concurrently (std::async), so concurrent DDS loads must not interleave. + static std::mutex loaderMutex; + std::lock_guard lock(loaderMutex); + stream->Seek(0, IoSeek::Set); m_dds.fourCC = ReadLE(stream); diff --git a/src/ui/backlogmenu.cpp b/src/ui/backlogmenu.cpp index a86c27e35..6cb1ca678 100644 --- a/src/ui/backlogmenu.cpp +++ b/src/ui/backlogmenu.cpp @@ -359,12 +359,17 @@ void BacklogMenu::AddMessage(Vm::BufferOffsetContext scrCtx, auto onClick = [this](auto* btn) { return MenuButtonOnClick(btn); }; + // An entry can hold no glyphs (SG0 adds empty lines with SetRevMes); then + // its bounds give the bottom. + const auto entryBottom = [](const EntryType& entry) { + return entry.Page.Glyphs.empty() + ? entry.Bounds.Bottom() + : entry.Page.Glyphs.back().DestRect.Bottom(); + }; const glm::vec2 entryPos = - Entries.empty() - ? EntriesStart - : glm::vec2(EntriesStart.x, - Entries.front().Page.Glyphs.back().DestRect.Bottom() + - EntryYPadding); + Entries.empty() ? EntriesStart + : glm::vec2(EntriesStart.x, + entryBottom(Entries.front()) + EntryYPadding); if (Entries.full()) { ItemsHeight -= Entries.back().Bounds.Height + EntryYPadding; diff --git a/src/ui/gamespecific.cpp b/src/ui/gamespecific.cpp index 90589c965..ad4671e3a 100644 --- a/src/ui/gamespecific.cpp +++ b/src/ui/gamespecific.cpp @@ -49,6 +49,7 @@ void Init() { break; case GameSpecificType::Dash: break; + case GameSpecificType::SG0: case GameSpecificType::None: break; } @@ -65,6 +66,7 @@ void NonGameplayUpdate(float dt) { case GameSpecificType::RNE: case GameSpecificType::CC: case GameSpecificType::CCLCC: + case GameSpecificType::SG0: case GameSpecificType::None: break; } @@ -94,6 +96,7 @@ void Update(float dt) { break; case GameSpecificType::Dash: break; + case GameSpecificType::SG0: case GameSpecificType::None: break; } @@ -108,6 +111,7 @@ void RenderEarlyMain() { case GameSpecificType::RNE: case GameSpecificType::CC: case GameSpecificType::CCLCC: + case GameSpecificType::SG0: case GameSpecificType::None: break; } @@ -141,6 +145,7 @@ void RenderMain() { break; case GameSpecificType::CCLCC: break; + case GameSpecificType::SG0: case GameSpecificType::None: break; } @@ -191,6 +196,7 @@ void RenderLayer(uint32_t layer) { break; case GameSpecificType::Dash: break; + case GameSpecificType::SG0: case GameSpecificType::None: break; } diff --git a/src/ui/ui.h b/src/ui/ui.h index 8f7e70edc..068457b5b 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -26,6 +26,7 @@ enum class SystemMenuType : int { CHLCC, MO8, CCLCC, + SG0, }; enum class SaveMenuType : int { None, @@ -33,6 +34,7 @@ enum class SaveMenuType : int { MO8, CHLCC, CCLCC, + SG0, }; enum class SysMesBoxType : int { None, @@ -42,6 +44,7 @@ enum class SysMesBoxType : int { MO6TW, Darling, CC, + SG0, }; enum class TitleMenuType : int { None, @@ -52,6 +55,7 @@ enum class TitleMenuType : int { MO8, CC, CCLCC, + SG0, }; enum class OptionsMenuType : int { None, @@ -59,22 +63,26 @@ enum class OptionsMenuType : int { MO8, CHLCC, CCLCC, + SG0, }; enum class TipsMenuType : int { None, MO6TW, CHLCC, CCLCC, + SG0, }; enum class ClearListMenuType : int { None, MO6TW, CHLCC, CCLCC, + SG0, }; enum class LibraryMenuType : int { None, CCLCC, + SG0, }; enum class AlbumMenuType : int { None, @@ -102,6 +110,7 @@ enum class TrophyMenuType : int { enum class HelpMenuType : int { None, CCLCC, + SG0, }; enum class GameSpecificType : int { None, @@ -110,6 +119,7 @@ enum class GameSpecificType : int { CCLCC, RNE, Dash, + SG0, }; int constexpr MaxExtraMenus = 10; diff --git a/src/video/ffmpegplayer.cpp b/src/video/ffmpegplayer.cpp index e162e3726..2ff42c1d2 100644 --- a/src/video/ffmpegplayer.cpp +++ b/src/video/ffmpegplayer.cpp @@ -1,5 +1,7 @@ #include "ffmpegplayer.h" +#include +#include #include #include #include @@ -786,6 +788,11 @@ void FFmpegPlayer::Update(float dt) { VideoTexture = Renderer->CreateYUVFrame( (float)VideoStream->CodecContext.width(), (float)VideoStream->CodecContext.height()); + } else if (frame->Frame.pixelFormat() == AV_PIX_FMT_YUVA420P) { + VideoTexture = Renderer->CreateYUVFrame( + (float)VideoStream->CodecContext.width(), + (float)VideoStream->CodecContext.height() * + (IsAlpha ? 2.0f : 1.0f)); } else if (frame->Frame.pixelFormat() == AV_PIX_FMT_NV12) { VideoTexture = Renderer->CreateNV12Frame( (float)VideoStream->CodecContext.width(), @@ -800,9 +807,15 @@ void FFmpegPlayer::Update(float dt) { nv12Frame->Submit(frame->Frame.data(0), frame->Frame.raw()->linesize[0], frame->Frame.data(1), frame->Frame.raw()->linesize[1]); } else if (frame->Frame.pixelFormat() == AV_PIX_FMT_YUV420P) { - auto& yuvFrame = std::get(VideoTexture); - yuvFrame->Submit(frame->Frame.data(0), frame->Frame.data(1), - frame->Frame.data(2)); + if (frame->Frame.raw()->color_range == AVCOL_RANGE_JPEG) { + SubmitRepackedFrame(frame->Frame.raw()); + } else { + auto& yuvFrame = std::get(VideoTexture); + yuvFrame->Submit(frame->Frame.data(0), frame->Frame.data(1), + frame->Frame.data(2)); + } + } else if (frame->Frame.pixelFormat() == AV_PIX_FMT_YUVA420P) { + SubmitRepackedFrame(frame->Frame.raw()); } else { ImpLog(LogLevel::Warning, LogChannel::Video, "Unsupported frame pixel format, video will not display!\n"); @@ -816,6 +829,61 @@ void FFmpegPlayer::Update(float dt) { } } +void FFmpegPlayer::SubmitRepackedFrame(const AVFrame* frame) { + // Video-range lookup tables: luma 0..255 -> 16..235, chroma 0..255 -> + // 16..240 around 128. Identity for frames already in video range. + static const auto tables = [] { + std::array, 4> t{}; + for (int v = 0; v < 256; v++) { + t[0][v] = static_cast(v); + t[1][v] = static_cast(16 + (v * 219 + 127) / 255); + t[2][v] = static_cast( + 128 + ((v - 128) * 224 + (v >= 128 ? 127 : -127)) / 255); + } + t[3] = t[1]; // alpha is read through the luma conversion + return t; + }(); + const bool fullRange = frame->color_range == AVCOL_RANGE_JPEG; + const auto& lumaTable = tables[fullRange ? 1 : 0]; + const auto& chromaTable = tables[fullRange ? 2 : 0]; + + const int width = frame->width; + const int height = frame->height; + const int chromaWidth = (width + 1) / 2; + const int chromaHeight = (height + 1) / 2; + const bool withAlpha = IsAlpha && frame->format == AV_PIX_FMT_YUVA420P; + const int halves = withAlpha ? 2 : 1; + PackedLuma.resize(static_cast(width) * height * halves); + PackedCb.assign(static_cast(chromaWidth) * chromaHeight * halves, + 128); + PackedCr.assign(PackedCb.size(), 128); + + const auto copyPlane = [](uint8_t* dst, const uint8_t* src, int srcStride, + int rowBytes, int rows, + const std::array& table) { + for (int y = 0; y < rows; y++) { + const uint8_t* in = src + static_cast(y) * srcStride; + uint8_t* out = dst + static_cast(y) * rowBytes; + for (int x = 0; x < rowBytes; x++) out[x] = table[in[x]]; + } + }; + copyPlane(PackedLuma.data(), frame->data[0], frame->linesize[0], width, + height, lumaTable); + copyPlane(PackedCb.data(), frame->data[1], frame->linesize[1], chromaWidth, + chromaHeight, chromaTable); + copyPlane(PackedCr.data(), frame->data[2], frame->linesize[2], chromaWidth, + chromaHeight, chromaTable); + if (withAlpha) { + // The shader reads alpha through the limited-range YUV->RGB matrix with + // neutral chroma, so store it as video-range luma (16..235). + copyPlane(PackedLuma.data() + static_cast(width) * height, + frame->data[3], frame->linesize[3], width, height, tables[3]); + } + + std::get(VideoTexture) + ->Submit(PackedLuma.data(), PackedCb.data(), PackedCr.data()); +} + void FFmpegPlayer::UpdateSubtitles() { for (auto& subtitleStream : EmbeddedSubtitleStreams) { Video::AVDecodedItem subtitle; diff --git a/src/video/ffmpegplayer.h b/src/video/ffmpegplayer.h index af1871a71..597ac21f9 100644 --- a/src/video/ffmpegplayer.h +++ b/src/video/ffmpegplayer.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -99,6 +100,14 @@ class FFmpegPlayer : public VideoPlayer { std::variant VideoTexture; bool IsAlpha = false; + // Frames the video shader cannot take as they are get repacked: full-range + // YUV (all of SG0's Bink 2 movies) is scaled to the video range the shader + // converts from, and YUVA420P (Bink 2 with alpha) gets the alpha video + // layout, picture rows on top and alpha as luma below. + void SubmitRepackedFrame(const AVFrame* frame); + std::vector PackedLuma; + std::vector PackedCb; + std::vector PackedCr; bool Looping = false; bool ReaderEOF = false; bool PlaybackStarted = false; diff --git a/src/vm/inst_controlflow.cpp b/src/vm/inst_controlflow.cpp index 791ee75dd..f6db62264 100644 --- a/src/vm/inst_controlflow.cpp +++ b/src/vm/inst_controlflow.cpp @@ -99,6 +99,59 @@ VmInstruction(InstCallFar) { "CallFar error, call stack overflow.\n"); } } +// SG0: Call / CallFar guarded by a flag, operands +// (u8 value, expr flagId, label[, ...], u16 returnId). The return id is always +// present in the bytecode, so it is consumed even when the call is not taken. +// Every SG0 use is CallIfFlag(1, flag, label), whose callee clears that flag, +// so the call is taken when GetFlag(flagId) == value (as in InstFlagOnJump). +VmInstruction(InstCallIfFlag) { + StartInstruction; + PopUint8(value); + PopExpression(flagId); + PopLocalLabel(labelAdr); + PopUint16(retNum); + + if (GetFlag(flagId) != (bool)value) return; + + if (thread->CallStackDepth == MaxCallStackDepth) { + ImpLog(LogLevel::Error, LogChannel::VM, + "CallIfFlag error, call stack overflow.\n"); + return; + } + if (Profile::Vm::UseReturnIds) { + thread->ReturnIds[thread->CallStackDepth] = retNum; + } else { + thread->ReturnAddresses[thread->CallStackDepth] = thread->IpOffset; + } + thread->ReturnScriptBufferIds[thread->CallStackDepth++] = + thread->ScriptBufferId; + thread->IpOffset = labelAdr; +} +VmInstruction(InstCallFarIfFlag) { + StartInstruction; + PopUint8(value); + PopExpression(flagId); + PopExpression(scriptBufferId); + PopFarLabel(labelAdr, scriptBufferId); + PopUint16(retNum); + + if (GetFlag(flagId) != (bool)value) return; + + if (thread->CallStackDepth == MaxCallStackDepth) { + ImpLog(LogLevel::Error, LogChannel::VM, + "CallFarIfFlag error, call stack overflow.\n"); + return; + } + if (Profile::Vm::UseReturnIds) { + thread->ReturnIds[thread->CallStackDepth] = retNum; + } else { + thread->ReturnAddresses[thread->CallStackDepth] = thread->IpOffset; + } + thread->ReturnScriptBufferIds[thread->CallStackDepth++] = + thread->ScriptBufferId; + thread->IpOffset = labelAdr; + thread->ScriptBufferId = scriptBufferId; +} VmInstruction(InstReturn) { StartInstruction; if (thread->CallStackDepth) { @@ -254,6 +307,17 @@ VmInstruction(InstClickOnJump) { PopExpression(arg2); PopUint16(labelNum); uint32_t labelAdr = ScriptGetLabelAddress(thread->ScriptBufferId, labelNum); + if (Profile::Vm::GameInstructionSet == InstructionSet::SG0) { + // SG0 passes a mouse button mask: 1 left, 2 right; 0x200000 (logo and + // title waits) skips on a left click as well. + const bool left = Input::MouseButtonWentDown[SDL_BUTTON_LEFT]; + const bool right = Input::MouseButtonWentDown[SDL_BUTTON_RIGHT]; + if (((arg2 & 1) && left) || ((arg2 & 2) && right) || + ((arg2 & 0x200000) && left)) { + thread->IpOffset = labelAdr; + } + return; + } if (Input::KeyboardButtonWentDown[SDL_SCANCODE_D]) { thread->IpOffset = labelAdr; } diff --git a/src/vm/inst_controlflow.h b/src/vm/inst_controlflow.h index 06ff454f0..7aa5ca5d2 100644 --- a/src/vm/inst_controlflow.h +++ b/src/vm/inst_controlflow.h @@ -12,6 +12,8 @@ VmInstruction(InstIf); VmInstruction(InstCall); VmInstruction(InstJumpFar); VmInstruction(InstCallFar); +VmInstruction(InstCallIfFlag); +VmInstruction(InstCallFarIfFlag); VmInstruction(InstReturn); VmInstruction(InstReturnIfFlag); VmInstruction(InstLoop); diff --git a/src/vm/inst_dialogue.cpp b/src/vm/inst_dialogue.cpp index 8a167f5d0..6a9814374 100644 --- a/src/vm/inst_dialogue.cpp +++ b/src/vm/inst_dialogue.cpp @@ -950,7 +950,8 @@ VmInstruction(InstSel) { if (Profile::Vm::GameInstructionSet == InstructionSet::Dash || Profile::Vm::GameInstructionSet == InstructionSet::CC || Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch || - Profile::Vm::GameInstructionSet == InstructionSet::MO8) { + Profile::Vm::GameInstructionSet == InstructionSet::MO8 || + Profile::Vm::GameInstructionSet == InstructionSet::SG0) { PopUint16(savepointid); // SF_MESSAVEPOINT_SSP + dialog page's field 5 in decompile? // if (GetFlag(SF_MESSAVEPOINT_SSP + thread->DialoguePageId) == 0) { @@ -1134,7 +1135,8 @@ VmInstruction(InstNameID) { if (Profile::Vm::GameInstructionSet == InstructionSet::CC || Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch || Profile::Vm::GameInstructionSet == InstructionSet::MO8 || - Profile::Vm::GameInstructionSet == InstructionSet::CHN) { + Profile::Vm::GameInstructionSet == InstructionSet::CHN || + Profile::Vm::GameInstructionSet == InstructionSet::SG0) { PopLocalLabel(namePlateDataBlock); Sc3Stream namePlateData( &ScriptBuffers[thread->ScriptBufferId][namePlateDataBlock]); diff --git a/src/vm/inst_gamespecific.cpp b/src/vm/inst_gamespecific.cpp index d82e55bb0..736faf2cc 100644 --- a/src/vm/inst_gamespecific.cpp +++ b/src/vm/inst_gamespecific.cpp @@ -10,6 +10,8 @@ #include "../profile/vm.h" #include "../ui/gamespecific.h" +#include "../games/sg0/phone.h" +#include "../games/sg0/rine.h" #include "../games/cclcc/delusiontrigger.h" #include "../games/chlcc/delusiontrigger.h" @@ -763,6 +765,186 @@ VmInstruction(InstPhoneSG) { } } } +// STEINS;GATE 0 handlers. Operand layouts were fitted against the SG0 script +// data and agree with CommitteeOfZero/sc3ntist ZeroDisassembler.cpp. +// Semantics are unknown unless stated, so these only consume their operands +// and log. + +// 00 35: like InstSignIn, but SG0 encodes a one-byte argument. +VmInstruction(InstSignInSG0) { + StartInstruction; + PopUint8(arg1); + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction SignInSG0(arg1: {:d})\n", arg1); +} + +// 00 4C: u8 type, type 0 takes one expression. +VmInstruction(InstUnk004CSG0) { + StartInstruction; + PopUint8(type); + if (type == 0) { + PopExpression(arg1); + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction Unk004CSG0(type: {:d}, arg1: {:d})\n", type, + arg1); + } else { + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction Unk004CSG0(type: {:d})\n", type); + } +} + +// 00 50: u8 type; types 0 and 2 take two local labels, type 3 takes three. +// The title loop runs "PressStart(0, label, label)" right after the engine +// sets SF_TITLEEND, so types 0 and 3 jump to their first label like CC's +// InstPressStartNew does. +VmInstruction(InstPressStartSG0) { + StartInstruction; + PopUint8(type); + switch (type) { + case 0: + case 2: { + PopUint16(label1Num); + PopUint16(label2Num); + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction PressStartSG0(type: {:d}, label1: {:d}, " + "label2: {:d})\n", + type, label1Num, label2Num); + if (type == 0) { + thread->IpOffset = + ScriptGetLabelAddress(thread->ScriptBufferId, label1Num); + } + } break; + case 3: { + PopUint16(label1Num); + PopUint16(label2Num); + PopUint16(label3Num); + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction PressStartSG0(type: {:d}, label1: {:d}, " + "label2: {:d}, label3: {:d})\n", + type, label1Num, label2Num, label3Num); + thread->IpOffset = + ScriptGetLabelAddress(thread->ScriptBufferId, label1Num); + } break; + default: + ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, + "STUB instruction PressStartSG0(type: {:d})\n", type); + break; + } +} + +// 10 37: SG0 phone (RINE / mail). Label operands are label numbers in the +// current script: type 0 names seven data blocks, types 0xA and 0xD name code. +VmInstruction(InstPhoneSG0) { + StartInstruction; + PopUint8(type); + using UI::SG0::RineState; + switch (type) { + case 0x00: { // PhoneInit: the seven _phone tables + uint16_t labels[7]; + for (uint16_t& label : labels) { + PopUint16(labelNum); + label = labelNum; + } + if (UI::SG0::PhonePtr) { + UI::SG0::PhonePtr->LoadTables(thread->ScriptBufferId, labels); + } + RineState.LoadParticipants(thread->ScriptBufferId, labels[4]); + } break; + case 0x01: // SessionBegin: the player opened the phone + if (UI::SG0::PhonePtr) UI::SG0::PhonePtr->BeginSession(); + break; + case 0x02: // SessionTick: the phone UI sets flag 2604 when done + break; + case 0x03: // SessionEnd + if (UI::SG0::PhonePtr) UI::SG0::PhonePtr->EndSession(); + break; + case 0x04: // IdleTick + break; + case 0x0A: { // SetReplyMenu: the block is read when the player replies + PopUint16(labelNum); + RineState.SetReplyMenu(thread->ScriptBufferId, labelNum); + } break; + case 0x0B: // EndBlock: ends a reply branch, or cancels the pending menu + if (RineState.IsBranchThread(thread)) { + RineState.EndBranch(thread); + BlockThread; + } else { + RineState.CancelReplyMenu(); + } + break; + case 0x0C: { // MenuHeader (only reached if the VM runs the menu block) + PopExpression(sender); + PopExpression(kind); + (void)sender; + (void)kind; + } break; + case 0x0D: { // MenuOption + PopExpression(value); + PopUint16(labelNum); + (void)value; + (void)labelNum; + } break; + case 0x0E: // MenuEnd + break; + case 0x0F: { // QueueMessage: a branch message, after a typing pause + PopExpression(sender); + PopExpression(kind); + PopExpression(value); + if (RineState.IsBranchThread(thread) && !RineState.TypingDone()) { + ResetInstruction; + BlockThread; + break; + } + RineState.AddMessage(sender, kind, value); + } break; + case 0x28: // ClearAllRine + RineState.ClearAll(); + break; + case 0x29: // ThreadRefresh: the phone UI lays the thread out itself + break; + case 0x2A: { // NewThread(owner) + PopExpression(owner); + RineState.NewThread(owner); + } break; + case 0x2B: // AddMember + case 0x2C: { // AddMemberUnread + PopExpression(participant); + RineState.AddMember(participant); + } break; + case 0x2D: { // unused + PopExpression(arg1); + (void)arg1; + } break; + case 0x2E: { // AddMessage(sender, kind, value) + PopExpression(sender); + PopExpression(kind); + PopExpression(value); + RineState.AddMessage(sender, kind, value); + } break; + case 0x2F: // ClearInput + RineState.ClearInput(); + break; + case 0x30: { // SetInputText(text) + PopExpression(text); + RineState.SetInput(text); + } break; + case 0x31: { // unused + PopExpression(arg1); + PopExpression(arg2); + (void)arg1; + (void)arg2; + } break; + case 0x32: { // SelectTextList(_phone label) + PopUint16(labelNum); + RineState.SelectTextList(labelNum); + } break; + default: + ImpLog(LogLevel::Error, LogChannel::VMStub, + "Unknown PhoneSG0 type {:d}, operand length unknown\n", type); + break; + } +} + VmInstruction(InstMail) { StartInstruction; PopUint8(type); diff --git a/src/vm/inst_gamespecific.h b/src/vm/inst_gamespecific.h index d4a7146e4..ef2ee783f 100644 --- a/src/vm/inst_gamespecific.h +++ b/src/vm/inst_gamespecific.h @@ -26,6 +26,10 @@ VmInstruction(InstUnk1037Noah); VmInstruction(InstUnk1037MO7); VmInstruction(InstMapSystem); VmInstruction(InstPhoneSG); +VmInstruction(InstSignInSG0); +VmInstruction(InstUnk004CSG0); +VmInstruction(InstPressStartSG0); +VmInstruction(InstPhoneSG0); VmInstruction(InstMail); VmInstruction(InstMtrg); VmInstruction(InstUnk1038Darling); diff --git a/src/vm/inst_graphics2d.cpp b/src/vm/inst_graphics2d.cpp index 1ee2e9e7c..1f568f0ec 100644 --- a/src/vm/inst_graphics2d.cpp +++ b/src/vm/inst_graphics2d.cpp @@ -130,6 +130,8 @@ VmInstruction(InstBGload) { BlockThread; } else if (ScrWork[SW_BG1NO + ScrWorkBgStructSize * actualBufId] != backgroundId) { + ImpLog(LogLevel::Debug, LogChannel::VM, "BGload buffer {:d} bg {:d}\n", + bufferId, backgroundId); ScrWork[SW_BG1NO + ScrWorkBgStructSize * actualBufId] = backgroundId; Backgrounds2D[bgBufId]->LoadAsync(backgroundId); ResetInstruction; diff --git a/src/vm/inst_misc.cpp b/src/vm/inst_misc.cpp index c50faf69f..8a98b7bb3 100644 --- a/src/vm/inst_misc.cpp +++ b/src/vm/inst_misc.cpp @@ -23,6 +23,10 @@ #include "../games/cc/titlemenu.h" #include "../games/cclcc/systemmenu.h" #include "../games/cclcc/helpmenu.h" +#include "../games/sg0/librarymenu.h" +#include "../games/sg0/cutin.h" +#include "../video/videosystem.h" +#include "../io/vfs.h" namespace Impacto { namespace Vm { @@ -113,6 +117,9 @@ VmInstruction(InstSystemMenu) { BlockThread; } } + if (Profile::Vm::GameInstructionSet == InstructionSet::SG0) { + UI::SystemMenuPtr->Init(); + } if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { ScrWork[SW_SYSMENUCTMAX] = 32; } @@ -327,6 +334,11 @@ VmInstruction(InstAlbum) { "STUB instruction Album(type: EXmenuInit)\n"); break; case 1: // EXmenuMain + if (auto* library = + dynamic_cast(UI::LibraryMenuPtr)) { + library->MainTick(); + break; + } ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, "STUB instruction Album(type: EXmenuMain)\n"); break; @@ -378,8 +390,16 @@ VmInstruction(InstSaveMenu) { switch (type) { case 0: { // SaveMenuInit PopUint8(arg1); - UI::SaveMenuPtr->ActiveMenuType = - magic_enum::enum_cast(arg1); + if (Profile::Vm::GameInstructionSet == InstructionSet::SG0) { + // STEINS;GATE 0 numbers the pages load 0, save 1, quick load 2. + constexpr UI::SaveMenuPageType sg0Pages[] = { + UI::SaveMenuPageType::Load, UI::SaveMenuPageType::Save, + UI::SaveMenuPageType::QuickLoad}; + if (arg1 < 3) UI::SaveMenuPtr->ActiveMenuType = sg0Pages[arg1]; + } else { + UI::SaveMenuPtr->ActiveMenuType = + magic_enum::enum_cast(arg1); + } ScrWork[SW_SAVEFILESTATUS] = 0; // Block input during animation @@ -545,6 +565,7 @@ VmInstruction(InstTitleMenuNew) { break; case InstructionSet::LCCSwitch: case InstructionSet::CC: + case InstructionSet::SG0: case InstructionSet::CHN: { int mainState; int startTransitionState; @@ -562,6 +583,20 @@ VmInstruction(InstTitleMenuNew) { startTransitionState = StartTransition; } + if (Profile::Vm::GameInstructionSet == InstructionSet::SG0 && + +Profile::Game::GameFeatures & +GameFeature::Video && + !Video::Players[0]->IsPlaying) { + // SG0's title runs over intro.bk2 (_Startup_win plays it once, + // on movie layer 1, before entering the title loop). Keep it + // looping while the title is up, also after the EXTRA movie + // library has played a movie on layer 3000/4000. + constexpr int TitleMovieId = 32; + Io::Stream* stream; + if (Io::VfsOpen("movie", TitleMovieId, &stream) == IoError_OK) { + ScrWork[SW_MOVIEPRI] = 1; + Video::Players[0]->Play(stream, true, false); + } + } if (ScrWork[SW_TITLEMODE] == mainState) { if (!UI::TitleMenuPtr->AllowsScriptInput) { ResetInstruction; @@ -659,6 +694,11 @@ VmInstruction(InstSetCutin) { PopExpression(arg1); PopExpression(arg2); PopExpression(arg3); + if (Profile::Vm::GameInstructionSet == InstructionSet::SG0 && + type < SG0::Cutins.size()) { + SG0::Cutins[type] = {.Enabled = arg1 != 0, .BufferMask = arg2}; + return; + } ImpLogSlow(LogLevel::Warning, LogChannel::VMStub, "STUB instruction SetCutin(type: {:d}, arg1: {:d}, arg2: {:d}, " "arg3: {:d})\n", diff --git a/src/vm/inst_system.cpp b/src/vm/inst_system.cpp index 48d2b3f24..b3973a6ff 100644 --- a/src/vm/inst_system.cpp +++ b/src/vm/inst_system.cpp @@ -104,6 +104,8 @@ VmInstruction(InstScriptLoad) { StartInstruction; PopExpression(bufferId); PopExpression(scriptId); + ImpLog(LogLevel::Debug, LogChannel::VM, + "ScriptLoad buffer {:d} script {:d}\n", bufferId, scriptId); if (Profile::Vm::UseMsbStrings) { LoadMsb(bufferId, scriptId); if (!Profile::Vm::UseSeparateMsbArchive) scriptId += 1; @@ -706,6 +708,7 @@ VmInstruction(InstSystemMes) { case InstructionSet::Dash: case InstructionSet::CC: case InstructionSet::LCCSwitch: + case InstructionSet::SG0: PopUint8(id); sysMesId = id; break; @@ -1010,7 +1013,8 @@ VmInstruction(InstMSinit) { Profile::Vm::GameInstructionSet == InstructionSet::CHLCC) { memset(&FlagWork, 0, 500); memset(&ScrWork, 0, 24000); - } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC) { + } else if (Profile::Vm::GameInstructionSet == InstructionSet::CC || + Profile::Vm::GameInstructionSet == InstructionSet::SG0) { memset(&FlagWork, 0, 1000); memset(&ScrWork, 0, 32000); } else if (Profile::Vm::GameInstructionSet == InstructionSet::LCCSwitch) { @@ -1213,6 +1217,11 @@ VmInstruction(InstAutoSave) { SaveSystem::FlushWorkingSaveEntry(SaveSystem::SaveType::Quick, *quicksaveEntries, saveType); SaveSystem::SaveThumbnailData(); + // SG0's scripts never write the save file after a quick save, so persist + // it here (asynchronously) or it would be lost when the game closes. + if (Profile::Vm::GameInstructionSet == InstructionSet::SG0) { + SaveSystem::WriteSaveFile(); + } } }; diff --git a/src/vm/opcodetables_sg0.h b/src/vm/opcodetables_sg0.h new file mode 100644 index 000000000..f0d26e1c9 --- /dev/null +++ b/src/vm/opcodetables_sg0.h @@ -0,0 +1,804 @@ +// STEINS;GATE 0 (Steam) opcode tables. +// Base: opcodetables_cc.h (CHAOS;CHILD). Entries that differ from CC were +// fitted against the SG0 script data so that every label block, call site and +// return-address table entry of all 178 .scx scripts decodes in alignment. +// Operand layouts not covered by an existing handler follow +// CommitteeOfZero/sc3ntist src/parser/ZeroDisassembler.cpp. +#include "inst_system.h" +#include "inst_controlflow.h" +#include "inst_graphics2d.h" +#include "inst_graphics3d.h" +#include "inst_dialogue.h" +#include "inst_sound.h" +#include "inst_movie.h" +#include "inst_gamespecific.h" +#include "inst_misc.h" + +// clang-format off + +namespace Impacto { + +namespace Vm { + +InstructionProc inline constexpr OpcodeTableSystem_SG0[256] = { + InstEnd, // 00 00 + InstCreateThread, // 00 01 + InstKillThread, // 00 02 + InstReset, // 00 03 + InstScriptLoad, // 00 04 + InstWait, // 00 05 + InstHalt, // 00 06 + InstJump, // 00 07 + InstJumpTable, // 00 08 + InstGetLabelAdr, // 00 09 + InstIf, // 00 0A + InstCall, // 00 0B + InstJumpFar, // 00 0C + InstCallFar, // 00 0D + InstReturn, // 00 0E + InstLoop, // 00 0F + InstFlagOnJump, // 00 10 + InstFlagOnWait, // 00 11 + InstSetFlag, // 00 12 + InstResetFlag, // 00 13 + InstCopyFlag, // 00 14 + InstKeyOnJump, // 00 15 + InstKeyWait, // 00 16 + InstKeyWaitTimer, // 00 17 + InstMemberWrite, // 00 18 + InstThreadControl, // 00 19 + InstGetSelfPointer, // 00 1A + InstLoadJump, // 00 1B + InstVsync, // 00 1C + InstTest, // 00 1D + InstThreadControlStore, // 00 1E + InstSwitch, // 00 1F + InstCase, // 00 20 + InstBGMplay, // 00 21 + InstBGMstop, // 00 22 + InstSEplay, // 00 23 + InstSEstop, // 00 24 + InstPadAct, // 00 25 + InstSSEplay, // 00 26 + InstSSEstop, // 00 27 + InstCopyThreadWork, // 00 28 + InstUPLmenuUI, // 00 29 + InstSave, // 00 2A + InstSaveIconLoad, // 00 2B + InstBGMflag, // 00 2C + InstUPLxTitle, // 00 2D + InstPresence, // 00 2E + InstSetAchievement, // 00 2F + InstSetPlayer, // 00 30 + InstVoiceTableLoadMaybe, // 00 31 + InstSetPadCustom, // 00 32 + InstMwait, // 00 33 + InstTerminate, // 00 34 + InstSignInSG0, // 00 35 + InstAchievementIcon, // 00 36 + InstVoicePlay, // 00 37 + InstVoiceStopNew, // 00 38 + InstVoicePlayWait, // 00 39 + InstBGMduelPlay, // 00 3A + InstSNDpause, // 00 3B + InstSEplayWait, // 00 3C + InstDebugPrint, // 00 3D + InstResetSoundAll, // 00 3E + InstSNDloadStop, // 00 3F + InstBGMstopWait, // 00 40 + InstUnk0041, // 00 41 + InstSetX360SysMesPos, // 00 42 + InstSystemMes, // 00 43 + InstSystemMenu, // 00 44 + InstGetNowTime, // 00 45 + InstGetSystemStatus, // 00 46 + InstReboot, // 00 47 + InstReloadScript, // 00 48 + InstReloadScriptMenu, // 00 49 + InstDebugEditer, // 00 4A + InstSysSeload, // 00 4B + InstUnk004CSG0, // 00 4C + InstSysVoicePlay, // 00 4D + InstPadActEx, // 00 4E + InstDebugSetup, // 00 4F + InstPressStartSG0, // 00 50 + InstGlobalSystemMessage, // 00 51 + InstUnk0052, // 00 52 + InstControlOnJump, // 00 53 + InstCallIfFlag, // 00 54 + InstControlOnJump, // 00 55 + InstCallFarIfFlag, // 00 56 + InstReturnIfFlag, // 00 57 + InstClickOnJump, // 00 58 + InstKeyboardOnJump, // 00 59 + InstDummy, // 00 5A + InstDummy, // 00 5B + InstFlagOffReturn, // 00 5C + InstDummy, // 00 5D + InstDummy, // 00 5E + InstPackFileAddBind, // 00 5F + InstDummy, // 00 60 + InstDummy, // 00 61 + InstDummy, // 00 62 + InstDummy, // 00 63 + InstDummy, // 00 64 + InstDummy, // 00 65 + InstDummy, // 00 66 + InstDummy, // 00 67 + InstDummy, // 00 68 + InstDummy, // 00 69 + InstDummy, // 00 6A + InstDummy, // 00 6B + InstDummy, // 00 6C + InstDummy, // 00 6D + InstDummy, // 00 6E + InstDummy, // 00 6F + InstDummy, // 00 70 + InstDummy, // 00 71 + InstDummy, // 00 72 + InstDummy, // 00 73 + InstDummy, // 00 74 + InstDummy, // 00 75 + InstDummy, // 00 76 + InstDummy, // 00 77 + InstDummy, // 00 78 + InstDummy, // 00 79 + InstDummy, // 00 7A + InstDummy, // 00 7B + InstDummy, // 00 7C + InstDummy, // 00 7D + InstDummy, // 00 7E + InstDummy, // 00 7F + InstDummy, // 00 80 + InstDummy, // 00 81 + InstDummy, // 00 82 + InstDummy, // 00 83 + InstDummy, // 00 84 + InstDummy, // 00 85 + InstDummy, // 00 86 + InstDummy, // 00 87 + InstDummy, // 00 88 + InstDummy, // 00 89 + InstDummy, // 00 8A + InstDummy, // 00 8B + InstDummy, // 00 8C + InstDummy, // 00 8D + InstDummy, // 00 8E + InstDummy, // 00 8F + InstDummy, // 00 90 + InstDummy, // 00 91 + InstDummy, // 00 92 + InstDummy, // 00 93 + InstDummy, // 00 94 + InstDummy, // 00 95 + InstDummy, // 00 96 + InstDummy, // 00 97 + InstDummy, // 00 98 + InstDummy, // 00 99 + InstDummy, // 00 9A + InstDummy, // 00 9B + InstDummy, // 00 9C + InstDummy, // 00 9D + InstDummy, // 00 9E + InstDummy, // 00 9F + InstDummy, // 00 A0 + InstDummy, // 00 A1 + InstDummy, // 00 A2 + InstDummy, // 00 A3 + InstDummy, // 00 A4 + InstDummy, // 00 A5 + InstDummy, // 00 A6 + InstDummy, // 00 A7 + InstDummy, // 00 A8 + InstDummy, // 00 A9 + InstDummy, // 00 AA + InstDummy, // 00 AB + InstDummy, // 00 AC + InstDummy, // 00 AD + InstDummy, // 00 AE + InstDummy, // 00 AF + InstDummy, // 00 B0 + InstDummy, // 00 B1 + InstDummy, // 00 B2 + InstDummy, // 00 B3 + InstDummy, // 00 B4 + InstDummy, // 00 B5 + InstDummy, // 00 B6 + InstDummy, // 00 B7 + InstDummy, // 00 B8 + InstDummy, // 00 B9 + InstDummy, // 00 BA + InstDummy, // 00 BB + InstDummy, // 00 BC + InstDummy, // 00 BD + InstDummy, // 00 BE + InstDummy, // 00 BF + InstDummy, // 00 C0 + InstDummy, // 00 C1 + InstDummy, // 00 C2 + InstDummy, // 00 C3 + InstDummy, // 00 C4 + InstDummy, // 00 C5 + InstDummy, // 00 C6 + InstDummy, // 00 C7 + InstDummy, // 00 C8 + InstDummy, // 00 C9 + InstDummy, // 00 CA + InstDummy, // 00 CB + InstDummy, // 00 CC + InstDummy, // 00 CD + InstDummy, // 00 CE + InstDummy, // 00 CF + InstDummy, // 00 D0 + InstDummy, // 00 D1 + InstDummy, // 00 D2 + InstDummy, // 00 D3 + InstDummy, // 00 D4 + InstDummy, // 00 D5 + InstDummy, // 00 D6 + InstDummy, // 00 D7 + InstDummy, // 00 D8 + InstDummy, // 00 D9 + InstDummy, // 00 DA + InstDummy, // 00 DB + InstDummy, // 00 DC + InstDummy, // 00 DD + InstDummy, // 00 DE + InstDummy, // 00 DF + InstDummy, // 00 E0 + InstDummy, // 00 E1 + InstDummy, // 00 E2 + InstDummy, // 00 E3 + InstDummy, // 00 E4 + InstDummy, // 00 E5 + InstDummy, // 00 E6 + InstDummy, // 00 E7 + InstDummy, // 00 E8 + InstDummy, // 00 E9 + InstDummy, // 00 EA + InstDummy, // 00 EB + InstDummy, // 00 EC + InstDummy, // 00 ED + InstDummy, // 00 EE + InstDummy, // 00 EF + InstDummy, // 00 F0 + InstDummy, // 00 F1 + InstDummy, // 00 F2 + InstDummy, // 00 F3 + InstDummy, // 00 F4 + InstDummy, // 00 F5 + InstDummy, // 00 F6 + InstDummy, // 00 F7 + InstDummy, // 00 F8 + InstDummy, // 00 F9 + InstDummy, // 00 FA + InstDummy, // 00 FB + InstDummy, // 00 FC + InstDummy, // 00 FD + InstDummy, // 00 FE + InstDummy // 00 FF +}; + +InstructionProc inline constexpr OpcodeTableGraph_SG0[256] = { + InstCreateSurf, // 01 00 + InstReleaseSurf, // 01 01 + InstLoadPic, // 01 02 + InstReleaseSurf, // 01 03 + InstSurfFill, // 01 04 + InstCalc, // 01 05 + InstMesViewFlag, // 01 06 + InstSetMesWinPri, // 01 07 + InstMesSync, // 01 08 + InstMesSetID, // 01 09 + InstMesCls, // 01 0A + InstMesVoiceWait, // 01 0B + InstMes, // 01 0C + InstMesMain, // 01 0D + InstSetMesModeFormat, // 01 0E + InstSetNGmoji, // 01 0F + InstMesRev, // 01 10 + InstMessWindow, // 01 11 + InstSel, // 01 12 + InstSelect, // 01 13 + InstSysSel, // 01 14 + InstSysSelect, // 01 15 + InstDummy, // 01 16 + InstDummy, // 01 17 + InstDummy, // 01 18 + InstDummy, // 01 19 + InstDummy, // 01 1A + InstDummy, // 01 1B + InstDummy, // 01 1C + InstDummy, // 01 1D + InstDummy, // 01 1E + InstLoadFontWidths, // 01 1F + InstSCcapture, // 01 20 + InstSetTextTable, // 01 21 + InstPlayMovie, // 01 22 + InstMovieMain, // 01 23 + InstLoadMovie, // 01 24 + InstSetRevMes, // 01 25 + InstPlayMovieMemory, // 01 26 + InstPlayMovie, // 01 27 + InstMovieMain, // 01 28 + InstLoadMovie, // 01 29 + InstPlayMovieMemory, // 01 2A + InstSFDpause, // 01 2B + InstPlayMovie, // 01 2C + InstUnk012D, // 01 2D + InstDummy, // 01 2E + InstDummy, // 01 2F + InstDummy, // 01 30 + InstDummy, // 01 31 + InstDummy, // 01 32 + InstDummy, // 01 33 + InstDummy, // 01 34 + InstDummy, // 01 35 + InstDummy, // 01 36 + InstDummy, // 01 37 + InstDummy, // 01 38 + InstDummy, // 01 39 + InstDummy, // 01 3A + InstDummy, // 01 3B + InstDummy, // 01 3C + InstDummy, // 01 3D + InstDummy, // 01 3E + InstDummy, // 01 3F + InstDummy, // 01 40 + InstDummy, // 01 41 + InstDummy, // 01 42 + InstDummy, // 01 43 + InstDummy, // 01 44 + InstDummy, // 01 45 + InstDummy, // 01 46 + InstDummy, // 01 47 + InstDummy, // 01 48 + InstDummy, // 01 49 + InstDummy, // 01 4A + InstDummy, // 01 4B + InstDummy, // 01 4C + InstDummy, // 01 4D + InstDummy, // 01 4E + InstDummy, // 01 4F + InstDummy, // 01 50 + InstDummy, // 01 51 + InstDummy, // 01 52 + InstDummy, // 01 53 + InstDummy, // 01 54 + InstDummy, // 01 55 + InstDummy, // 01 56 + InstDummy, // 01 57 + InstDummy, // 01 58 + InstDummy, // 01 59 + InstDummy, // 01 5A + InstDummy, // 01 5B + InstDummy, // 01 5C + InstDummy, // 01 5D + InstDummy, // 01 5E + InstDummy, // 01 5F + InstDummy, // 01 60 + InstDummy, // 01 61 + InstDummy, // 01 62 + InstDummy, // 01 63 + InstDummy, // 01 64 + InstDummy, // 01 65 + InstDummy, // 01 66 + InstDummy, // 01 67 + InstDummy, // 01 68 + InstDummy, // 01 69 + InstDummy, // 01 6A + InstDummy, // 01 6B + InstDummy, // 01 6C + InstDummy, // 01 6D + InstDummy, // 01 6E + InstDummy, // 01 6F + InstDummy, // 01 70 + InstDummy, // 01 71 + InstDummy, // 01 72 + InstDummy, // 01 73 + InstDummy, // 01 74 + InstDummy, // 01 75 + InstDummy, // 01 76 + InstDummy, // 01 77 + InstDummy, // 01 78 + InstDummy, // 01 79 + InstDummy, // 01 7A + InstDummy, // 01 7B + InstDummy, // 01 7C + InstDummy, // 01 7D + InstDummy, // 01 7E + InstDummy, // 01 7F + InstDummy, // 01 80 + InstDummy, // 01 81 + InstDummy, // 01 82 + InstDummy, // 01 83 + InstDummy, // 01 84 + InstDummy, // 01 85 + InstDummy, // 01 86 + InstDummy, // 01 87 + InstDummy, // 01 88 + InstDummy, // 01 89 + InstDummy, // 01 8A + InstDummy, // 01 8B + InstDummy, // 01 8C + InstDummy, // 01 8D + InstDummy, // 01 8E + InstDummy, // 01 8F + InstDummy, // 01 90 + InstDummy, // 01 91 + InstDummy, // 01 92 + InstDummy, // 01 93 + InstDummy, // 01 94 + InstDummy, // 01 95 + InstDummy, // 01 96 + InstDummy, // 01 97 + InstDummy, // 01 98 + InstDummy, // 01 99 + InstDummy, // 01 9A + InstDummy, // 01 9B + InstDummy, // 01 9C + InstDummy, // 01 9D + InstDummy, // 01 9E + InstDummy, // 01 9F + InstDummy, // 01 A0 + InstDummy, // 01 A1 + InstDummy, // 01 A2 + InstDummy, // 01 A3 + InstDummy, // 01 A4 + InstDummy, // 01 A5 + InstDummy, // 01 A6 + InstDummy, // 01 A7 + InstDummy, // 01 A8 + InstDummy, // 01 A9 + InstDummy, // 01 AA + InstDummy, // 01 AB + InstDummy, // 01 AC + InstDummy, // 01 AD + InstDummy, // 01 AE + InstDummy, // 01 AF + InstDummy, // 01 B0 + InstDummy, // 01 B1 + InstDummy, // 01 B2 + InstDummy, // 01 B3 + InstDummy, // 01 B4 + InstDummy, // 01 B5 + InstDummy, // 01 B6 + InstDummy, // 01 B7 + InstDummy, // 01 B8 + InstDummy, // 01 B9 + InstDummy, // 01 BA + InstDummy, // 01 BB + InstDummy, // 01 BC + InstDummy, // 01 BD + InstDummy, // 01 BE + InstDummy, // 01 BF + InstDummy, // 01 C0 + InstDummy, // 01 C1 + InstDummy, // 01 C2 + InstDummy, // 01 C3 + InstDummy, // 01 C4 + InstDummy, // 01 C5 + InstDummy, // 01 C6 + InstDummy, // 01 C7 + InstDummy, // 01 C8 + InstDummy, // 01 C9 + InstDummy, // 01 CA + InstDummy, // 01 CB + InstDummy, // 01 CC + InstDummy, // 01 CD + InstDummy, // 01 CE + InstDummy, // 01 CF + InstDummy, // 01 D0 + InstDummy, // 01 D1 + InstDummy, // 01 D2 + InstDummy, // 01 D3 + InstDummy, // 01 D4 + InstDummy, // 01 D5 + InstDummy, // 01 D6 + InstDummy, // 01 D7 + InstDummy, // 01 D8 + InstDummy, // 01 D9 + InstDummy, // 01 DA + InstDummy, // 01 DB + InstDummy, // 01 DC + InstDummy, // 01 DD + InstDummy, // 01 DE + InstDummy, // 01 DF + InstDummy, // 01 E0 + InstDummy, // 01 E1 + InstDummy, // 01 E2 + InstDummy, // 01 E3 + InstDummy, // 01 E4 + InstDummy, // 01 E5 + InstDummy, // 01 E6 + InstDummy, // 01 E7 + InstDummy, // 01 E8 + InstDummy, // 01 E9 + InstDummy, // 01 EA + InstDummy, // 01 EB + InstDummy, // 01 EC + InstDummy, // 01 ED + InstDummy, // 01 EE + InstDummy, // 01 EF + InstDummy, // 01 F0 + InstDummy, // 01 F1 + InstDummy, // 01 F2 + InstDummy, // 01 F3 + InstDummy, // 01 F4 + InstDummy, // 01 F5 + InstDummy, // 01 F6 + InstDummy, // 01 F7 + InstDummy, // 01 F8 + InstDummy, // 01 F9 + InstDummy, // 01 FA + InstDummy, // 01 FB + InstDummy, // 01 FC + InstDummy, // 01 FD + InstDummy, // 01 FE + InstDummy // 01 FF +}; + +InstructionProc inline constexpr OpcodeTableUser1_SG0[256] = { + InstMSinit, // 10 00 + InstBGload, // 10 01 + InstBGswap, // 10 02 + InstBGsetColor, // 10 03 + InstBGsetLink, // 10 04 + InstCHAload, // 10 05 + InstCHAswap, // 10 06 + InstBGcopy, // 10 07 + InstCHAcopy, // 10 08 + InstSaveSlot, // 10 09 + InstSystemMain, // 10 0A + InstCharaLayerLoad, // 10 0B + InstGameInfoInit, // 10 0C + InstCHAmove, // 10 0D + InstBGloadEx, // 10 0E + InstDummy, // 10 0F + InstBGrelease, // 10 10 + InstCHArelease, // 10 11 + InstClearFlagChk, // 10 12 + InstOption, // 10 13 + InstSystemDataReset, // 10 14 + InstDebugData, // 10 15 + InstGetCharaPause, // 10 16 + InstBGfadeExpInit, // 10 17 + InstDummy, // 10 18 + InstDummy, // 10 19 + InstHelp, // 10 1A + InstHelp, // 10 1B + InstAchievementMenu, // 10 1C + InstSoundMenu, // 10 1D + InstAllClear, // 10 1E + InstAlbum, // 10 1F + InstMovieMode, // 10 20 + InstClistInit, // 10 21 + InstAutoSave, // 10 22 + InstSaveMenu, // 10 23 + InstLoadData, // 10 24 + InstDummy, // 10 25 + InstDummy, // 10 26 + InstSetDic, // 10 27 + InstSetPlayMode, // 10 28 + InstSetEVflag, // 10 29 + InstSetCutin, // 10 2A + InstDummy, // 10 2B + InstDummy, // 10 2C + InstAchChkTitle, // 10 2D + InstSetSceneViewFlag, // 10 2E + InstChkClearFlag, // 10 2F + InstBGeffectWave, // 10 30 + InstGeotag, // 10 31 + InstNameID, // 10 32 + InstTips, // 10 33 + InstTitleMenuNew, // 10 34 + InstDummy, // 10 35 + InstBGeffect, // 10 36 + InstPhoneSG0, // 10 37 + InstMtrg, // 10 38 + InstTwipo_Dash, // 10 39 + InstYesNoTriggerCCLCC, // 10 3A + InstDummy, // 10 3B + InstDummy, // 10 3C + InstDummy, // 10 3D + InstDummy, // 10 3E + InstDummy, // 10 3F + InstScreenChange, // 10 40 + InstExitGame, // 10 41 + InstDummy, // 10 42 + InstDummy, // 10 43 + InstDummy, // 10 44 + InstDummy, // 10 45 + InstDummy, // 10 46 + InstDummy, // 10 47 + InstDummy, // 10 48 + InstDummy, // 10 49 + InstDummy, // 10 4A + InstDummy, // 10 4B + InstDummy, // 10 4C + InstDummy, // 10 4D + InstDummy, // 10 4E + InstDummy, // 10 4F + InstDummy, // 10 50 + InstDummy, // 10 51 + InstDummy, // 10 52 + InstDummy, // 10 53 + InstDummy, // 10 54 + InstDummy, // 10 55 + InstDummy, // 10 56 + InstDummy, // 10 57 + InstDummy, // 10 58 + InstDummy, // 10 59 + InstDummy, // 10 5A + InstDummy, // 10 5B + InstDummy, // 10 5C + InstDummy, // 10 5D + InstDummy, // 10 5E + InstDummy, // 10 5F + InstDummy, // 10 60 + InstDummy, // 10 61 + InstDummy, // 10 62 + InstDummy, // 10 63 + InstDummy, // 10 64 + InstDummy, // 10 65 + InstDummy, // 10 66 + InstDummy, // 10 67 + InstDummy, // 10 68 + InstDummy, // 10 69 + InstDummy, // 10 6A + InstDummy, // 10 6B + InstDummy, // 10 6C + InstDummy, // 10 6D + InstDummy, // 10 6E + InstDummy, // 10 6F + InstDummy, // 10 70 + InstDummy, // 10 71 + InstDummy, // 10 72 + InstDummy, // 10 73 + InstDummy, // 10 74 + InstDummy, // 10 75 + InstDummy, // 10 76 + InstDummy, // 10 77 + InstDummy, // 10 78 + InstDummy, // 10 79 + InstDummy, // 10 7A + InstDummy, // 10 7B + InstDummy, // 10 7C + InstDummy, // 10 7D + InstDummy, // 10 7E + InstDummy, // 10 7F + InstDummy, // 10 80 + InstDummy, // 10 81 + InstDummy, // 10 82 + InstDummy, // 10 83 + InstDummy, // 10 84 + InstDummy, // 10 85 + InstDummy, // 10 86 + InstDummy, // 10 87 + InstDummy, // 10 88 + InstDummy, // 10 89 + InstDummy, // 10 8A + InstDummy, // 10 8B + InstDummy, // 10 8C + InstDummy, // 10 8D + InstDummy, // 10 8E + InstDummy, // 10 8F + InstDummy, // 10 90 + InstDummy, // 10 91 + InstDummy, // 10 92 + InstDummy, // 10 93 + InstDummy, // 10 94 + InstDummy, // 10 95 + InstDummy, // 10 96 + InstDummy, // 10 97 + InstDummy, // 10 98 + InstDummy, // 10 99 + InstDummy, // 10 9A + InstDummy, // 10 9B + InstDummy, // 10 9C + InstDummy, // 10 9D + InstDummy, // 10 9E + InstDummy, // 10 9F + InstDummy, // 10 A0 + InstDummy, // 10 A1 + InstDummy, // 10 A2 + InstDummy, // 10 A3 + InstDummy, // 10 A4 + InstDummy, // 10 A5 + InstDummy, // 10 A6 + InstDummy, // 10 A7 + InstDummy, // 10 A8 + InstDummy, // 10 A9 + InstDummy, // 10 AA + InstDummy, // 10 AB + InstDummy, // 10 AC + InstDummy, // 10 AD + InstDummy, // 10 AE + InstDummy, // 10 AF + InstDummy, // 10 B0 + InstDummy, // 10 B1 + InstDummy, // 10 B2 + InstDummy, // 10 B3 + InstDummy, // 10 B4 + InstDummy, // 10 B5 + InstDummy, // 10 B6 + InstDummy, // 10 B7 + InstDummy, // 10 B8 + InstDummy, // 10 B9 + InstDummy, // 10 BA + InstDummy, // 10 BB + InstDummy, // 10 BC + InstDummy, // 10 BD + InstDummy, // 10 BE + InstDummy, // 10 BF + InstDummy, // 10 C0 + InstDummy, // 10 C1 + InstDummy, // 10 C2 + InstDummy, // 10 C3 + InstDummy, // 10 C4 + InstDummy, // 10 C5 + InstDummy, // 10 C6 + InstDummy, // 10 C7 + InstDummy, // 10 C8 + InstDummy, // 10 C9 + InstDummy, // 10 CA + InstDummy, // 10 CB + InstDummy, // 10 CC + InstDummy, // 10 CD + InstDummy, // 10 CE + InstDummy, // 10 CF + InstDummy, // 10 D0 + InstDummy, // 10 D1 + InstDummy, // 10 D2 + InstDummy, // 10 D3 + InstDummy, // 10 D4 + InstDummy, // 10 D5 + InstDummy, // 10 D6 + InstDummy, // 10 D7 + InstDummy, // 10 D8 + InstDummy, // 10 D9 + InstDummy, // 10 DA + InstDummy, // 10 DB + InstDummy, // 10 DC + InstDummy, // 10 DD + InstDummy, // 10 DE + InstDummy, // 10 DF + InstDummy, // 10 E0 + InstDummy, // 10 E1 + InstDummy, // 10 E2 + InstDummy, // 10 E3 + InstDummy, // 10 E4 + InstDummy, // 10 E5 + InstDummy, // 10 E6 + InstDummy, // 10 E7 + InstDummy, // 10 E8 + InstDummy, // 10 E9 + InstDummy, // 10 EA + InstDummy, // 10 EB + InstDummy, // 10 EC + InstDummy, // 10 ED + InstDummy, // 10 EE + InstDummy, // 10 EF + InstDummy, // 10 F0 + InstDummy, // 10 F1 + InstDummy, // 10 F2 + InstDummy, // 10 F3 + InstDummy, // 10 F4 + InstDummy, // 10 F5 + InstDummy, // 10 F6 + InstDummy, // 10 F7 + InstDummy, // 10 F8 + InstDummy, // 10 F9 + InstDummy, // 10 FA + InstDummy, // 10 FB + InstDummy, // 10 FC + InstDummy, // 10 FD + InstDummy, // 10 FE + InstDummy // 10 FF +}; + +} + +} + +// clang-format on \ No newline at end of file diff --git a/src/vm/vm.cpp b/src/vm/vm.cpp index e0ec54450..a14c3f5c9 100644 --- a/src/vm/vm.cpp +++ b/src/vm/vm.cpp @@ -19,6 +19,7 @@ #include "opcodetables_sgps3.h" #include "opcodetables_chn.h" #include "opcodetables_lccswitch.h" +#include "opcodetables_sg0.h" #include "../profile/game.h" #include "../profile/vm.h" #include "../profile/scriptinput.h" @@ -143,6 +144,12 @@ void Init() { break; } + case InstructionSet::SG0: { + OpcodeTableSystem = OpcodeTableSystem_SG0; + OpcodeTableGraph = OpcodeTableGraph_SG0; + OpcodeTableUser1 = OpcodeTableUser1_SG0; + break; + } default: { ImpLog(LogLevel::Fatal, LogChannel::VM, "Unsupported instruction set\n"); Window->Shutdown(); diff --git a/src/vm/vm.h b/src/vm/vm.h index 14010451f..3b578578b 100644 --- a/src/vm/vm.h +++ b/src/vm/vm.h @@ -22,6 +22,7 @@ enum class InstructionSet : int { MO8, CHN, LCCSwitch, + SG0, }; using InstructionProc = auto (*)(Sc3VmThread* thread, float dt) -> void;