diff --git a/CMakeLists.txt b/CMakeLists.txt index cb1e1918..af341594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ if( COMMAND cmake_policy ) endif( COMMAND cmake_policy ) SET( CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeScripts ) +option( ENABLE_WX "Build the wxWidgets port" ON ) option( ENABLE_SDL "Build the SDL port" ON ) option( ENABLE_GTK "Build the GTK+ GUI" ON ) option( ENABLE_DEBUGGER "Enable the debugger" ON ) @@ -63,6 +64,17 @@ FIND_PACKAGE ( SDL REQUIRED ) if( ENABLE_LINK ) FIND_PACKAGE ( SFML REQUIRED ) endif( ENABLE_LINK ) +# set the standard libraries all ports use +SET(VBAMCORE_LIBS + vbamcore + fex + ${SDL_LIBRARY} + ${SFML_LIBRARY} + ${OPENGL_LIBRARIES} + ${ZLIB_LIBRARY} + ${PNG_LIBRARY}) + + # Disable looking for GTK if not going to build the GTK frontend # so that pkg-config is not required @@ -111,9 +123,20 @@ ENDIF( NOT SYSCONFDIR ) ADD_DEFINITIONS (-DHAVE_NETINET_IN_H -DHAVE_ARPA_INET_H -DHAVE_ZLIB_H -DFINAL_VERSION -DSDL -DUSE_OPENGL -DSYSCONFDIR='"${SYSCONFDIR}"' -DWITH_LIRC='${WITHLIRC}') ADD_DEFINITIONS (-DVERSION='"${VERSION}"' -DPKGDATADIR='"${PKGDATADIR}"' -DPACKAGE='') -if( NOT ENABLE_LINK ) +if( ENABLE_LINK ) + FIND_LIBRARY(RT_LIB rt) + IF(RT_LIB) + SET(CMAKE_REQUIRED_LIBRARIES ${RT_LIB}) + SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${RT_LIB}) + ENDIF(RT_LIB) + INCLUDE(CheckFunctionExists) + CHECK_FUNCTION_EXISTS(sem_timedwait SEM_TIMEDWAIT) + IF( SEM_TIMEDWAIT) + ADD_DEFINITIONS (-DHAVE_SEM_TIMEDWAIT) + ENDIF( SEM_TIMEDWAIT) +else( ENABLE_LINK ) ADD_DEFINITIONS (-DNO_LINK) -endif( NOT ENABLE_LINK ) +endif( ENABLE_LINK ) # The debugger is enabled by default if( NOT ENABLE_DEBUGGER ) @@ -132,6 +155,21 @@ if( ENABLE_NLS ) SET( LOCALEDIR ${CMAKE_INSTALL_PREFIX}/share/locale ) ADD_DEFINITIONS ( -DENABLE_NLS ) ADD_DEFINITIONS ( -DLOCALEDIR=\\\"${LOCALEDIR}\\\" ) + # for now, only GBALink.cpp uses gettext() directly + IF(ENABLE_LINK) + FIND_PATH(LIBINTL_INC libintl.h ) + FIND_LIBRARY(LIBINTL_LIB intl ) + IF(LIBINTL_LIB) + SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${LIBINTL_LIB}) + SET(VBAMCORE_LIBS ${VBAMCORE_LIBS} ${LIBINTL_LIB}) + ENDIF(LIBINTL_LIB) + INCLUDE(CheckFunctionExists) + CHECK_FUNCTION_EXISTS(gettext GETTEXT_FN) + IF(NOT LIBINTL_INC OR NOT GETTEXT_FN) + message( SEND_ERROR "NLS requires libintl" ) + ENDIF(NOT LIBINTL_INC OR NOT GETTEXT_FN) + INCLUDE_DIRECTORIES(${LIBINTL_INC}) + ENDIF(ENABLE_LINK) endif( ENABLE_NLS ) # Compiler flags @@ -256,6 +294,7 @@ SET(SRC_FILTERS src/filters/interframe.cpp src/filters/pixel.cpp src/filters/scanline.cpp + src/filters/simpleFilter.cpp ) SET(SRC_HQ_C @@ -270,6 +309,13 @@ SET(SRC_HQ_ASM src/filters/hq/asm/hq3x32.cpp ) +if( ENABLE_ASM_SCALERS ) + SET(SRC_FILTERS ${SRC_FILTERS} ${SRC_HQ_ASM}) +else( ENABLE_ASM_SCALERS ) + SET(SRC_FILTERS ${SRC_FILTERS} ${SRC_HQ_C}) + ADD_DEFINITIONS ( -DNO_ASM ) +endif( ENABLE_ASM_SCALERS ) + SET(SRC_GTK src/gtk/configfile.cpp src/gtk/main.cpp @@ -303,12 +349,6 @@ if( ENABLE_DEBUGGER ) ) endif( ENABLE_DEBUGGER ) -if( ENABLE_ASM_SCALERS ) - SET(SRC_HQ ${SRC_HQ_ASM}) -else( ENABLE_ASM_SCALERS ) - SET(SRC_HQ ${SRC_HQ_C}) -endif( ENABLE_ASM_SCALERS ) - INCLUDE_DIRECTORIES( ${ZLIB_INCLUDE_DIR} fex @@ -354,7 +394,6 @@ IF( ENABLE_SDL ) vbam WIN32 ${SRC_SDL} - ${SRC_HQ} ) IF( WIN32 ) @@ -367,15 +406,9 @@ IF( ENABLE_SDL ) TARGET_LINK_LIBRARIES ( vbam - vbamcore - ${SDL_LIBRARY} - ${ZLIB_LIBRARY} - ${PNG_LIBRARY} - ${OPENGL_LIBRARY} + ${VBAMCORE_LIBS} ${WIN32_LIBRARIES} ${LIRC_CLIENT_LIBRARY} - ${SFML_LIBRARY} - fex ) INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/vbam DESTINATION bin) @@ -394,14 +427,9 @@ IF( ENABLE_GTK ) TARGET_LINK_LIBRARIES ( gvbam - vbamcore - ${ZLIB_LIBRARY} - ${PNG_LIBRARY} - ${SDL_LIBRARY} + ${VBAMCORE_LIBS} ${GTKMM_LIBRARIES} ${GTKGLMM_LIBRARIES} - ${SFML_LIBRARY} - fex ) INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/gvbam DESTINATION bin) @@ -410,8 +438,20 @@ IF( ENABLE_GTK ) INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/gtk/ui DESTINATION ${DATA_INSTALL_DIR} PATTERN ".svn" EXCLUDE) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/vba-over.ini DESTINATION ${DATA_INSTALL_DIR}) +ENDIF( ENABLE_GTK ) + +IF( ENABLE_WX ) + # since this has generated source files, it's easier to just + # make from the subdir + # otherwise out-of-tree builds have trouble + + add_subdirectory(src/wx) + +ENDIF( ENABLE_WX ) + +if( ENABLE_GTK OR ENABLE_WX ) # Native Language Support if( ENABLE_NLS ) add_subdirectory(po) endif( ENABLE_NLS ) -ENDIF( ENABLE_GTK ) +endif( ENABLE_GTK OR ENABLE_WX ) diff --git a/CMakeScripts/FindDirectX.cmake b/CMakeScripts/FindDirectX.cmake new file mode 100644 index 00000000..85aee211 --- /dev/null +++ b/CMakeScripts/FindDirectX.cmake @@ -0,0 +1,48 @@ +# -*- cmake -*- + +# mostly taken from emeraldviewer +if (WIN32) + find_path(DIRECTX_INCLUDE_DIR dxdiag.h + "$ENV{DXSDK_DIR}/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Include" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Include" + "C:/DX90SDK/Include" + "$ENV{PROGRAMFILES}/DX90SDK/Include" + ) + if (DIRECTX_INCLUDE_DIR) + include_directories(${DIRECTX_INCLUDE_DIR}) + if (DIRECTX_FIND_QUIETLY) + message(STATUS "Found DirectX include: ${DIRECTX_INCLUDE_DIR}") + endif (DIRECTX_FIND_QUIETLY) + else (DIRECTX_INCLUDE_DIR) + message(FATAL_ERROR "Could not find DirectX SDK Include") + endif (DIRECTX_INCLUDE_DIR) + + + find_path(DIRECTX_LIBRARY_DIR dxguid.lib + "$ENV{DXSDK_DIR}/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Lib/x86" + "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Lib/x86" + "C:/DX90SDK/Lib" + "$ENV{PROGRAMFILES}/DX90SDK/Lib" + ) + if (DIRECTX_LIBRARY_DIR) + if (DIRECTX_FIND_QUIETLY) + message(STATUS "Found DirectX include: ${DIRECTX_LIBRARY_DIR}") + endif (DIRECTX_FIND_QUIETLY) + else (DIRECTX_LIBRARY_DIR) + message(FATAL_ERROR "Could not find DirectX SDK Libraries") + endif (DIRECTX_LIBRARY_DIR) + +endif (WIN32) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DirectX DEFAULT_MSG DIRECTX_LIBRARY_DIR DIRECTX_INCLUDE_DIR) diff --git a/debian/changelog b/debian/changelog index 320137ff..6e39cfbf 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +vbam (1.8.0.1016-1) unstable; urgency=low + + * Use new version from upstream, with wxwidgets patches + + -- Thomas J. Moore Sun, 22 May 2011 14:08:05 -0500 + vbam (1.8.0.1001-1) unstable; urgency=low * Use new version from upstream diff --git a/debian/control b/debian/control index 8ad9b588..b7abfb34 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: vbam Section: otherosfs Priority: optional Maintainer: Fernando Tarlá Cardoso Lemos -Build-Depends: debhelper (>= 7), cmake, nasm [i386 amd64], libsdl1.2-dev, libgl-dev, libgtkmm-2.4-dev, libgtkglextmm-x11-1.2-dev +Build-Depends: debhelper (>= 7), cmake, nasm [i386 amd64], libsdl1.2-dev, libgl-dev, libgtkmm-2.4-dev, libgtkglextmm-x11-1.2-dev, libavcodec-dev, libavformat-dev, libswscale-dev, libavutil-dev, wx-common, libcairo2-dev, liblircclient-dev, libopenal-dev, libwxgtk2.8-dev Standards-Version: 3.8.1 Homepage: http://www.vbam.com @@ -17,6 +17,7 @@ Description: Nintendo Game Boy Advance emulator . This package does not provide a GUI version of VisualBoyAdvance-M. See the vbam-gtk package for the GTK+ version of this program. + See the vbam-wx package for the wxWidgets version of this program. Package: vbam-gtk Architecture: any @@ -27,4 +28,16 @@ Description: Nintendo Game Boy Advance emulator (GTK+ frontend) Boy Advance handheld console, in addition to the original Game Boy handhelds and its Super and Color variants. . - This package provides the GUI version of VisualBoyAdvance-M. + This package provides the GTK+ GUI version of VisualBoyAdvance-M. + +Package: vbam-wx +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Nintendo Game Boy Advance emulator (GTK+ frontend) + VisualBoyAdvance-M is a Nintendo Game Boy Emulator with high + compatibility with commercial games. It emulates the Nintendo Game + Boy Advance handheld console, in addition to the original Game Boy + handhelds and its Super and Color variants. + . + This package provides the wxWidgets GUI version of VisualBoyAdvance-M. + diff --git a/debian/rules b/debian/rules index 9c41deeb..9db603b2 100755 --- a/debian/rules +++ b/debian/rules @@ -23,16 +23,23 @@ endif builddir/Makefile: dh_testdir - mkdir -p builddir + mkdir -p builddir builddir-wx cd builddir && \ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="$(CFLAGS)" \ -DCMAKE_LD_FLAGS="-Wl,-z,defs" -DCMAKE_CXX_FLAGS="$(CXXFLAGS)" \ + -DENABLE_LIRC=ON -DENABLE_WX=OFF \ + -DCMAKE_SKIP_RPATH=ON $(EXTRA_CMAKE_FLAGS) + cd builddir-wx && \ + cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="$(CFLAGS)" \ + -DCMAKE_LD_FLAGS="-Wl,-z,defs" -DCMAKE_CXX_FLAGS="$(CXXFLAGS)" \ + -DENABLE_LINK=ON -DENABLE_WX=ON -DENABLE_SDL=OFF -DENABLE_GTK=OFF\ -DCMAKE_SKIP_RPATH=ON $(EXTRA_CMAKE_FLAGS) build: build-stamp build-stamp: builddir/Makefile dh_testdir + $(MAKE) -C builddir-wx $(MAKE) -C builddir touch $@ @@ -40,7 +47,7 @@ clean: dh_testdir dh_testroot rm -f build-stamp - rm -rf builddir + rm -rf builddir builddir-wx dh_clean install: build @@ -49,6 +56,7 @@ install: build dh_prep dh_installdirs $(MAKE) -C builddir DESTDIR=$(CURDIR)/debian/tmp install + $(MAKE) -C builddir-wx DESTDIR=$(CURDIR)/debian/tmp install binary-indep: install diff --git a/debian/vbam-gtk.install b/debian/vbam-gtk.install index f62f5aa9..8ab49b56 100644 --- a/debian/vbam-gtk.install +++ b/debian/vbam-gtk.install @@ -1,4 +1,5 @@ usr/bin/gvbam usr/share/icons usr/share/vbam -usr/share/applications +usr/share/locale/*/*/gvbam.* +usr/share/applications/gvbam.desktop diff --git a/debian/vbam-wx.docs b/debian/vbam-wx.docs new file mode 100644 index 00000000..6f8c587f --- /dev/null +++ b/debian/vbam-wx.docs @@ -0,0 +1,2 @@ +doc/DevInfo.txt +doc/ips.htm diff --git a/debian/vbam-wx.install b/debian/vbam-wx.install new file mode 100644 index 00000000..ca0059aa --- /dev/null +++ b/debian/vbam-wx.install @@ -0,0 +1,4 @@ +usr/bin/wxvbam +usr/share/icons +usr/share/locale/*/*/wxvbam.* +usr/share/applications/wxvbam.desktop diff --git a/debian/vbam-wx.menu b/debian/vbam-wx.menu new file mode 100644 index 00000000..ed97c7ed --- /dev/null +++ b/debian/vbam-wx.menu @@ -0,0 +1,2 @@ +?package(vbam-wx):needs="X11" section="Applications/Emulators"\ + title="VisualBoyAdvance-M" command="/usr/bin/wxvbam" diff --git a/po/CMakeLists.txt b/po/CMakeLists.txt index 7de74d46..f308d58f 100644 --- a/po/CMakeLists.txt +++ b/po/CMakeLists.txt @@ -1,2 +1,47 @@ -GETTEXT_CREATE_TRANSLATIONS(gvbam.pot gvbam/cs.po gvbam/de.po gvbam/en.po gvbam/es_ES.po gvbam/fr.po gvbam/pt_BR.po gvbam/zh_CN.po) -ADD_DEPENDENCIES(gvbam translations) +IF(ENABLE_GTK) + GETTEXT_CREATE_TRANSLATIONS(gvbam.pot gvbam/cs.po gvbam/de.po gvbam/en.po gvbam/es_ES.po gvbam/fr.po gvbam/pt_BR.po gvbam/zh_CN.po) + ADD_DEPENDENCIES(gvbam translations) +ENDIF(ENABLE_GTK) +IF(ENABLE_WX) + # Extract message strings from xrc and source files (NLS only for 2nd cmd) + ADD_CUSTOM_COMMAND(OUTPUT wx-xrc-strings.h + COMMAND wxrc -g ../src/wx/wxvbam.xrc -o ${CMAKE_CURRENT_BINARY_DIR}/wx-xrc-strings.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ../src/wx/wxvbam.xrc) + SET(XGETTEXTCMD ${XGETTEXT} -k_ -kN_ -kwxTRANSLATE -s --copyright-holder=\"VBA-M development team\" --package-name=VBA-M --package-version=${VERSION}) + # note that GLOB wil return absolute paths unless otherwise requested + # and it runs in CMAKE_CURRENT_SOURCE_DIR + FILE(GLOB PO_SRC_FILES ../src/wx/*.cpp ../src/wx/*.h ../src/wx/widgets/*.cpp + ../src/Util.cpp ../src/gba/*.cpp ../src/gb/*.cpp) + ADD_CUSTOM_COMMAND(OUTPUT wxvbam.pot + COMMAND ${XGETTEXTCMD} -o wxvbam.pot ${PO_SRC_FILES} + COMMAND ${XGETTEXTCMD} -o wxvbam.pot ../src/wx/cmdtab.cpp + COMMAND ${XGETTEXTCMD} --from-code=utf-8 -j -o wxvbam.pot wx-xrc-strings.h + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS wx-xrc-strings.h) # there is no direct cmdtab.cpp rule + + # is there really any point in auto-generating en? + ADD_CUSTOM_COMMAND(OUTPUT wxvbam/en.po + COMMAND ${CMAKE_COMMAND} -E make_directory wxvbam + COMMAND ${MSGINIT} -i wxvbam.pot -o wxvbam/en.po --no-translator -l en_US.utf-8 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS wxvbam.pot) + + FILE(GLOB POFILES wxvbam/*.po) + IF(NOT POFILES) + SET(POFILES ${CMAKE_CURRENT_BINARY_DIR}/wxvbam/en.po) + ENDIF(NOT POFILES) + # only one GETTEXT_CREATE_TRANSLATIONS call can be made + # probably need to simulate effects of call or force spearate cmake + # invocations + IF(NOT ENABLE_GTK) + GETTEXT_CREATE_TRANSLATIONS(${CMAKE_CURRENT_BINARY_DIR}/wxvbam.pot ALL ${POFILES}) + # try to build wxvbam first so cmdtab is generated + ADD_DEPENDENCIES(translations wxvbam) + ELSE(NOT ENABLE_GTK) + MESSAGE(WARNING "wxWidgets translations overridden by GTK.\nBuild again without GTK to correct.") + ADD_CUSTOM_TARGET(extra_translations ALL DEPENDS ${POFILES}) + ADD_DEPENDENCIES(extra_translations wxvbam) + ENDIF(NOT ENABLE_GTK) +ENDIF(ENABLE_WX) + diff --git a/src/System.h b/src/System.h index f23821d8..9ec0c859 100644 --- a/src/System.h +++ b/src/System.h @@ -68,7 +68,11 @@ extern void Sm60FPS_Init(); extern bool Sm60FPS_CanSkipFrame(); extern void Sm60FPS_Sleep(); extern void DbgMsg(const char *msg, ...); +#ifdef SDL +#define winlog log +#else extern void winlog(const char *,...); +#endif extern void (*dbgOutput)(const char *s, u32 addr); extern void (*dbgSignal)(int sig,int number); diff --git a/src/Util.cpp b/src/Util.cpp index c77ff256..e83def95 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -436,12 +436,17 @@ static bool utilIsImage(const char *file) } #ifdef WIN32 -#include +#include #endif IMAGE_TYPE utilFindType(const char *file) { char buffer [2048]; + utilFindType(file, buffer); +} + +IMAGE_TYPE utilFindType(const char *file, char (&buffer)[2048]) +{ #ifdef WIN32 DWORD dwNum = MultiByteToWideChar (CP_ACP, 0, file, -1, NULL, 0); wchar_t *pwText; @@ -453,24 +458,24 @@ IMAGE_TYPE utilFindType(const char *file) MultiByteToWideChar (CP_ACP, 0, file, -1, pwText, dwNum ); char* file_conv = fex_wide_to_path( pwText); delete []pwText; - if ( !utilIsImage( file_conv ) ) // TODO: utilIsArchive() instead? - { +// if ( !utilIsImage( file_conv ) ) // TODO: utilIsArchive() instead? +// { fex_t* fe = scan_arc(file_conv,utilIsImage,buffer); if(!fe) return IMAGE_UNKNOWN; fex_close(fe); file = buffer; - } +// } free(file_conv); #else - if ( !utilIsImage( file ) ) // TODO: utilIsArchive() instead? - { +// if ( !utilIsImage( file ) ) // TODO: utilIsArchive() instead? +// { fex_t* fe = scan_arc(file,utilIsImage,buffer); if(!fe) return IMAGE_UNKNOWN; fex_close(fe); file = buffer; - } +// } #endif return utilIsGBAImage(file) ? IMAGE_GBA : IMAGE_GB; } diff --git a/src/Util.h b/src/Util.h index cee770be..7393e7a2 100644 --- a/src/Util.h +++ b/src/Util.h @@ -23,6 +23,7 @@ bool utilIsGBImage(const char *); bool utilIsGzipFile(const char *); void utilStripDoubleExtension(const char *, char *); IMAGE_TYPE utilFindType(const char *); +IMAGE_TYPE utilFindType(const char *, char (&)[2048]); u8 *utilLoad(const char *, bool (*)(const char*), u8 *, int &); void utilPutDword(u8 *, u32); diff --git a/src/filters/interframe.cpp b/src/filters/interframe.cpp index b3ee723f..b72b78df 100644 --- a/src/filters/interframe.cpp +++ b/src/filters/interframe.cpp @@ -41,12 +41,12 @@ void InterframeCleanup() } #ifdef MMX -static void SmartIB_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) +static void SmartIB_MMX(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) { - u16 *src0 = (u16 *)srcPtr; - u16 *src1 = (u16 *)frm1; - u16 *src2 = (u16 *)frm2; - u16 *src3 = (u16 *)frm3; + u16 *src0 = (u16 *)srcPtr + starty * srcPitch / 2; + u16 *src1 = (u16 *)frm1 + srcPitch * starty / 2; + u16 *src2 = (u16 *)frm2 + srcPitch * starty / 2; + u16 *src3 = (u16 *)frm3 + srcPitch * starty / 2; int count = width >> 2; @@ -159,24 +159,24 @@ static void SmartIB_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) } #endif -void SmartIB(u8 *srcPtr, u32 srcPitch, int width, int height) +void SmartIB(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) { if(frm1 == NULL) { Init(); } #ifdef MMX if(cpu_mmx) { - SmartIB_MMX(srcPtr, srcPitch, width, height); + SmartIB_MMX(srcPtr, srcPitch, width, starty, height); return; } #endif u16 colorMask = ~RGB_LOW_BITS_MASK; - u16 *src0 = (u16 *)srcPtr; - u16 *src1 = (u16 *)frm1; - u16 *src2 = (u16 *)frm2; - u16 *src3 = (u16 *)frm3; + u16 *src0 = (u16 *)srcPtr + starty * srcPitch / 2; + u16 *src1 = (u16 *)frm1 + srcPitch * starty / 2; + u16 *src2 = (u16 *)frm2 + srcPitch * starty / 2; + u16 *src3 = (u16 *)frm3 + srcPitch * starty / 2; int sPitch = srcPitch >> 1; @@ -201,13 +201,18 @@ void SmartIB(u8 *srcPtr, u32 srcPitch, int width, int height) frm2 = temp; } -#ifdef MMX -static void SmartIB32_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) +void SmartIB(u8 *srcPtr, u32 srcPitch, int width, int height) { - u32 *src0 = (u32 *)srcPtr; - u32 *src1 = (u32 *)frm1; - u32 *src2 = (u32 *)frm2; - u32 *src3 = (u32 *)frm3; + SmartIB(srcPtr, srcPitch, width, 0, height); +} + +#ifdef MMX +static void SmartIB32_MMX(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) +{ + u32 *src0 = (u32 *)srcPtr + starty * srcPitch / 4; + u32 *src1 = (u32 *)frm1 + starty * srcPitch / 4; + u32 *src2 = (u32 *)frm2 + starty * srcPitch / 4; + u32 *src3 = (u32 *)frm3 + starty * srcPitch / 4; int count = width >> 1; @@ -320,22 +325,22 @@ static void SmartIB32_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) } #endif -void SmartIB32(u8 *srcPtr, u32 srcPitch, int width, int height) +void SmartIB32(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) { if(frm1 == NULL) { Init(); } #ifdef MMX if(cpu_mmx) { - SmartIB32_MMX(srcPtr, srcPitch, width, height); + SmartIB32_MMX(srcPtr, srcPitch, width, starty, height); return; } #endif - u32 *src0 = (u32 *)srcPtr; - u32 *src1 = (u32 *)frm1; - u32 *src2 = (u32 *)frm2; - u32 *src3 = (u32 *)frm3; + u32 *src0 = (u32 *)srcPtr + starty * srcPitch / 4; + u32 *src1 = (u32 *)frm1 + starty * srcPitch / 4; + u32 *src2 = (u32 *)frm2 + starty * srcPitch / 4; + u32 *src3 = (u32 *)frm3 + starty * srcPitch / 4; u32 colorMask = 0xfefefe; @@ -362,11 +367,16 @@ void SmartIB32(u8 *srcPtr, u32 srcPitch, int width, int height) frm2 = temp; } -#ifdef MMX -static void MotionBlurIB_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) +void SmartIB32(u8 *srcPtr, u32 srcPitch, int width, int height) { - u16 *src0 = (u16 *)srcPtr; - u16 *src1 = (u16 *)frm1; + SmartIB32(srcPtr, srcPitch, width, 0, height); +} + +#ifdef MMX +static void MotionBlurIB_MMX(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) +{ + u16 *src0 = (u16 *)srcPtr + starty * srcPitch / 2; + u16 *src1 = (u16 *)frm1 + starty * srcPitch / 2; int count = width >> 2; @@ -431,7 +441,7 @@ static void MotionBlurIB_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) } #endif -void MotionBlurIB(u8 *srcPtr, u32 srcPitch, int width, int height) +void MotionBlurIB(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) { if(frm1 == NULL) { Init(); @@ -439,15 +449,15 @@ void MotionBlurIB(u8 *srcPtr, u32 srcPitch, int width, int height) #ifdef MMX if(cpu_mmx) { - MotionBlurIB_MMX(srcPtr, srcPitch, width, height); + MotionBlurIB_MMX(srcPtr, srcPitch, width, starty, height); return; } #endif u16 colorMask = ~RGB_LOW_BITS_MASK; - u16 *src0 = (u16 *)srcPtr; - u16 *src1 = (u16 *)frm1; + u16 *src0 = (u16 *)srcPtr + starty * srcPitch / 2; + u16 *src1 = (u16 *)frm1 + starty * srcPitch / 2; int sPitch = srcPitch >> 1; @@ -462,11 +472,16 @@ void MotionBlurIB(u8 *srcPtr, u32 srcPitch, int width, int height) } } -#ifdef MMX -static void MotionBlurIB32_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) +void MotionBlurIB(u8 *srcPtr, u32 srcPitch, int width, int height) { - u32 *src0 = (u32 *)srcPtr; - u32 *src1 = (u32 *)frm1; + MotionBlurIB(srcPtr, srcPitch, width, 0, height); +} + +#ifdef MMX +static void MotionBlurIB32_MMX(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) +{ + u32 *src0 = (u32 *)srcPtr + starty * srcPitch / 4; + u32 *src1 = (u32 *)frm1 + starty * srcPitch / 4; int count = width >> 1; @@ -531,7 +546,7 @@ static void MotionBlurIB32_MMX(u8 *srcPtr, u32 srcPitch, int width, int height) } #endif -void MotionBlurIB32(u8 *srcPtr, u32 srcPitch, int width, int height) +void MotionBlurIB32(u8 *srcPtr, u32 srcPitch, int width, int starty, int height) { if(frm1 == NULL) { Init(); @@ -539,13 +554,13 @@ void MotionBlurIB32(u8 *srcPtr, u32 srcPitch, int width, int height) #ifdef MMX if(cpu_mmx) { - MotionBlurIB32_MMX(srcPtr, srcPitch, width, height); + MotionBlurIB32_MMX(srcPtr, srcPitch, width, starty, height); return; } #endif - u32 *src0 = (u32 *)srcPtr; - u32 *src1 = (u32 *)frm1; + u32 *src0 = (u32 *)srcPtr + starty * srcPitch / 4; + u32 *src1 = (u32 *)frm1 + starty * srcPitch / 4; u32 colorMask = 0xfefefe; @@ -561,3 +576,8 @@ void MotionBlurIB32(u8 *srcPtr, u32 srcPitch, int width, int height) pos++; } } + +void MotionBlurIB32(u8 *srcPtr, u32 srcPitch, int width, int height) +{ + MotionBlurIB32(srcPtr, srcPitch, width, 0, height); +} diff --git a/src/gb/GB.cpp b/src/gb/GB.cpp index 2258526a..50521104 100644 --- a/src/gb/GB.cpp +++ b/src/gb/GB.cpp @@ -21,7 +21,6 @@ extern u8 *pix; extern bool speedup; -bool gbUpdateSizes(); bool inBios = false; // debugging diff --git a/src/gb/gb.h b/src/gb/gb.h index 669c0c95..c2556144 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -17,7 +17,12 @@ typedef union { u16 W; } gbRegister; +extern gbRegister AF, BC, DE, HL, SP, PC; +extern u16 IFF; +int gbDis(char *, u16); + bool gbLoadRom(const char *); +bool gbUpdateSizes(); void gbEmulate(int); void gbWriteMemory(register u16, register u8); void gbDrawLine(); diff --git a/src/gb/gbGlobals.cpp b/src/gb/gbGlobals.cpp index 70e6225e..de944c5d 100644 --- a/src/gb/gbGlobals.cpp +++ b/src/gb/gbGlobals.cpp @@ -27,11 +27,11 @@ bool genericflashcardEnable = false; int gbCgbMode = 0; u16 gbColorFilter[32768]; -int gbColorOption = 0; +bool gbColorOption = false; int gbPaletteOption = 0; int gbEmulatorType = 0; -int gbBorderOn = 1; -int gbBorderAutomatic = 0; +bool gbBorderOn = true; +bool gbBorderAutomatic = false; int gbBorderLineSkip = 160; int gbBorderRowSkip = 0; int gbBorderColumnSkip = 0; diff --git a/src/gb/gbGlobals.h b/src/gb/gbGlobals.h index d6c19b6c..415c7455 100644 --- a/src/gb/gbGlobals.h +++ b/src/gb/gbGlobals.h @@ -25,11 +25,11 @@ extern u8 *gbMemoryMap[16]; extern int gbFrameSkip; extern u16 gbColorFilter[32768]; -extern int gbColorOption; +extern bool gbColorOption; extern int gbPaletteOption; extern int gbEmulatorType; -extern int gbBorderOn; -extern int gbBorderAutomatic; +extern bool gbBorderOn; +extern bool gbBorderAutomatic; extern int gbCgbMode; extern int gbSgbMode; extern int gbWindowLine; diff --git a/src/gba/GBA.cpp b/src/gba/GBA.cpp index 805cadd2..0bced723 100644 --- a/src/gba/GBA.cpp +++ b/src/gba/GBA.cpp @@ -113,7 +113,6 @@ bool fxOn = false; bool windowOn = false; int frameCount = 0; char buffer[1024]; -FILE *out = NULL; u32 lastTime = 0; int count = 0; @@ -2826,27 +2825,12 @@ void CPUUpdateRegister(u32 address, u16 value) case COMM_SIOCNT: StartLink(value); - /* - // old code path for no linking... - { - if (value & 0x80) { - value &= 0xff7f; - if ((value & 1) && (value & 0x4000)) { - UPDATE_REG(COMM_SIODATA8, 0xFF); - IF |= 0x80; - UPDATE_REG(0x202, IF); - value &= 0x7f7f; - } - } - UPDATE_REG(COMM_SIOCNT, value); - } - */ break; case COMM_SIODATA8: if (gba_link_enabled) LinkSSend(value); - UPDATE_REG(COMM_RCNT, value); + UPDATE_REG(COMM_SIODATA8, value); break; case 0x130: @@ -3452,27 +3436,6 @@ void CPUInterrupt() biosProtected[3] = 0xe5; } -#ifdef SDL -void log(const char *defaultMsg, ...) -{ - char buffer[2048]; - va_list valist; - - va_start(valist, defaultMsg); - vsprintf(buffer, defaultMsg, valist); - - if(out == NULL) { - out = fopen("trace.log","w"); - } - - fputs(buffer, out); - - va_end(valist); -} -#else -extern void winlog(const char *, ...); -#endif - void CPULoop(int ticks) { int clockTicks; @@ -3498,30 +3461,20 @@ void CPULoop(int ticks) #ifdef BKPT_SUPPORT if (debugger_last) { - sprintf(buffer, "R00=%08x R01=%08x R02=%08x R03=%08x R04=%08x R05=%08x R06=%08x R07=%08x R08=%08x R09=%08x R10=%08x R11=%08x R12=%08x R13=%08x R14=%08x R15=%08x R16=%08x R17=%08x\n", + winlog("R00=%08x R01=%08x R02=%08x R03=%08x R04=%08x R05=%08x R06=%08x R07=%08x R08=%08x R09=%08x R10=%08x R11=%08x R12=%08x R13=%08x R14=%08x R15=%08x R16=%08x R17=%08x\n", oldreg[0], oldreg[1], oldreg[2], oldreg[3], oldreg[4], oldreg[5], oldreg[6], oldreg[7], oldreg[8], oldreg[9], oldreg[10], oldreg[11], oldreg[12], oldreg[13], oldreg[14], oldreg[15], oldreg[16], oldreg[17]); } #endif - sprintf(buffer, "R00=%08x R01=%08x R02=%08x R03=%08x R04=%08x R05=%08x R06=%08x R07=%08x R08=%08x R09=%08x R10=%08x R11=%08x R12=%08x R13=%08x R14=%08x R15=%08x R16=%08x R17=%08x\n", + winlog("R00=%08x R01=%08x R02=%08x R03=%08x R04=%08x R05=%08x R06=%08x R07=%08x R08=%08x R09=%08x R10=%08x R11=%08x R12=%08x R13=%08x R14=%08x R15=%08x R16=%08x R17=%08x\n", reg[0].I, reg[1].I, reg[2].I, reg[3].I, reg[4].I, reg[5].I, reg[6].I, reg[7].I, reg[8].I, reg[9].I, reg[10].I, reg[11].I, reg[12].I, reg[13].I, reg[14].I, reg[15].I, reg[16].I, reg[17].I); -#ifdef SDL - log(buffer); -#else - winlog(buffer); -#endif } else if(!holdState) { - sprintf(buffer, "PC=%08x\n", armNextPC); -#ifdef SDL - log(buffer); -#else - winlog(buffer); -#endif + winlog("PC=%08x\n", armNextPC); } } #endif /* FINAL_VERSION */ diff --git a/src/gba/GBALink.cpp b/src/gba/GBALink.cpp index 1b0b6ea6..122a7b9d 100644 --- a/src/gba/GBALink.cpp +++ b/src/gba/GBALink.cpp @@ -1,25 +1,137 @@ // This file was written by denopqrihg +// with major changes by tjm +#ifndef NO_LINK // Joybus bool gba_joybus_enabled = false; // If disabled, gba core won't call any (non-joybus) link functions bool gba_link_enabled = false; -#ifndef NO_LINK -#ifdef _WIN32 -#include "../win32/stdafx.h" -#include "../win32/VBA.h" -#include "../win32/MainWnd.h" -#include "../win32/LinkOptions.h" -#include "../win32/Reg.h" -#endif +#define LOCAL_LINK_NAME "VBA link memory" +#define IP_LINK_PORT 5738 +#include #include #include "../common/Port.h" #include "GBA.h" #include "GBALink.h" #include "GBASockClient.h" +#ifdef ENABLE_NLS +#include +#define _(x) gettext(x) +#else +#define _(x) x +#endif +#define N_(x) x +#ifdef __WIN32__ +#include +#else +#include +#include +#include +#include +#include +#define ReleaseSemaphore(sem, nrel, orel) do { \ + for(int i = 0; i < nrel; i++) \ + sem_post(sem); \ +} while(0) +#define WAIT_TIMEOUT -1 +#ifdef HAVE_SEM_TIMEDWAIT +int WaitForSingleObject(sem_t *s, int t) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += t/1000; + ts.tv_nsec += (t%1000) * 1000000; + do { + if(!sem_timedwait(s, &ts)) + return 0; + } while(errno == EINTR); + return WAIT_TIMEOUT; +} + +// urg.. MacOSX has no sem_timedwait (POSIX) or semtimedop (SYSV) +// so we'll have to simulate it.. +// MacOSX also has no clock_gettime, and since both are "real-time", assume +// anyone who doesn't have one also doesn't have the other + +// 2 ways to do this: +// - poll & sleep loop +// - poll & wait for timer interrupt loop + +// the first consumes more CPU and requires selection of a good sleep value + +// the second may interfere with other timers running on system, and +// requires that a dummy signal handler be installed for SIGALRM +#else +#include +#ifndef TIMEDWAIT_ALRM +#define TIMEDWAIT_ALRM 1 +#endif +#if TIMEDWAIT_ALRM +#include +static void alrmhand(int sig) +{ +} +#endif +int WaitForSingleObject(sem_t *s, int t) +{ +#if !TIMEDWAIT_ALRM + struct timeval ts; + gettimeofday(&ts, NULL); + ts.tv_sec += t/1000; + ts.tv_usec += (t%1000) * 1000; +#else + struct sigaction sa, osa; + sigaction(SIGALRM, NULL, &osa); + sa = osa; + sa.sa_flags &= ~SA_RESTART; + sa.sa_handler = alrmhand; + sigaction(SIGALRM, &sa, NULL); + struct itimerval tv, otv; + tv.it_value.tv_sec = t / 1000; + tv.it_value.tv_usec = (t%1000) * 1000; + // this should be 0/0, but in the wait loop, it's possible to + // have the signal fire while not in sem_wait(). This will ensure + // another signal within 1ms + tv.it_interval.tv_sec = 0; + tv.it_interval.tv_usec = 999; + setitimer(ITIMER_REAL, &tv, &otv); +#endif + while(1) { +#if !TIMEDWAIT_ALRM + if(!sem_trywait(s)) + return 0; + struct timeval ts2; + gettimeofday(&ts2, NULL); + if(ts2.tv_sec > ts.tv_sec || (ts2.tv_sec == ts.tv_sec && + ts2.tv_usec > ts.tv_usec)) { + return WAIT_TIMEOUT; + } + // is .1 ms short enough? long enough? who knows? + struct timespec ts3; + ts3.tv_sec = 0; + ts3.tv_nsec = 100000; + nanosleep(&ts3, NULL); +#else + if(!sem_wait(s)) { + setitimer(ITIMER_REAL, &otv, NULL); + sigaction(SIGALRM, &osa, NULL); + return 0; + } + getitimer(ITIMER_REAL, &tv); + if(tv.it_value.tv_sec || tv.it_value.tv_usec > 999) + continue; + setitimer(ITIMER_REAL, &otv, NULL); + sigaction(SIGALRM, &osa, NULL); + break; +#endif + } + return WAIT_TIMEOUT; +} +#endif +#endif #define UPDATE_REG(address, value) WRITE16LE(((u16 *)&ioMem[address]),value) @@ -28,16 +140,27 @@ int linktime = 0; GBASockClient* dol = NULL; sf::IPAddress joybusHostAddr = sf::IPAddress::LocalHost; -#ifdef _MSC_VER // Hodgepodge u8 tspeed = 3; u8 transfer = 0; LINKDATA *linkmem = NULL; int linkid = 0, vbaid = 0; +#ifdef __WIN32__ HANDLE linksync[4]; +#else +sem_t *linksync[4]; +#endif int savedlinktime = 0; +#ifdef __WIN32__ HANDLE mmf = NULL; -char linkevent[] = "VBA link event "; +#else +int mmf = -1; +#endif +char linkevent[] = +#ifndef __WIN32__ + "/" +#endif + "VBA link event "; static int i, j; int linktimeout = 1000; LANLINKDATA lanlink; @@ -49,21 +172,32 @@ bool oncewait = false, after = false; // RFU crap (except for numtransfers note...should probably check that out) bool rfu_enabled = false; u8 rfu_cmd, rfu_qsend, rfu_qrecv; -int rfu_state, rfu_polarity, linktime2, rfu_counter, rfu_masterq; +int rfu_state, rfu_polarity, rfu_counter, rfu_masterq; // numtransfers seems to be used interchangeably with linkmem->numtransfers -// probably a bug? -int rfu_transfer_end, numtransfers = 0; +// in rfu code; probably a bug? +int rfu_transfer_end; +// in local comm, setting this keeps slaves from trying to communicate even +// when master isn't +u16 numtransfers = 0; u32 rfu_masterdata[32]; -// ??? +// time to end of single GBA's transfer, in 16.78 MHz clock ticks +// first index is GBA # int trtimedata[4][4] = { + // 9600 38400 57600 115200 {34080, 8520, 5680, 2840}, {65536, 16384, 10923, 5461}, {99609, 24903, 16602, 8301}, {133692, 33423, 22282, 11141} }; +// time to end of transfer +// for 3 slaves, this is time to transfer machine 4 +// for < 3 slaves, this is time to transfer last machine + time to detect lack +// of start bit from next slave +// first index is (# of slaves) - 1 int trtimeend[3][4] = { + // 9600 38400 57600 115200 {72527, 18132, 12088, 6044}, {106608, 26652, 17768, 8884}, {133692, 33423, 22282, 11141} @@ -73,17 +207,17 @@ int gbtime = 1024; int GetSIOMode(u16, u16); -DWORD WINAPI LinkClientThread(void *); -DWORD WINAPI LinkServerThread(void *); +void LinkClientThread(void *); +void LinkServerThread(void *); int StartServer(void); u16 StartRFU(u16); -char *MakeInstanceFilename(const char *Input) +const char *MakeInstanceFilename(const char *Input) { if (vbaid == 0) - return (char *)Input; + return Input; static char *result=NULL; if (result!=NULL) @@ -95,7 +229,6 @@ char *MakeInstanceFilename(const char *Input) return result; } - void StartLink(u16 value) { if (ioMem == NULL) @@ -107,62 +240,99 @@ void StartLink(u16 value) } switch (GetSIOMode(value, READ16LE(&ioMem[COMM_RCNT]))) { - case MULTIPLAYER: - if (value & 0x80) { - if (!linkid) { - if (!transfer) { - if (lanlink.active) - { - if (lanlink.connected) - { - linkdata[0] = READ16LE(&ioMem[COMM_SIODATA8]); - savedlinktime = linktime; - tspeed = value & 3; - ls.Send(); - transfer = 1; - linktime = 0; - UPDATE_REG(COMM_SIODATA32_L, linkdata[0]); - UPDATE_REG(COMM_SIODATA32_H, 0xffff); - WRITE32LE(&ioMem[0x124], 0xffffffff); - if (lanlink.speed&&oncewait == false) - ls.howmanytimes++; - after = false; - } - } - else if (linkmem->numgbas > 1) - { - ResetEvent(linksync[0]); - linkmem->linkcmd[0] = ('M' << 8) + (value & 3); - linkmem->linkdata[0] = READ16LE(&ioMem[COMM_SIODATA8]); - - if (linkmem->numtransfers != 0) - linkmem->lastlinktime = linktime; - else - linkmem->lastlinktime = 0; - - if ((++linkmem->numtransfers) == 0) - linkmem->numtransfers = 2; - transfer = 1; - linktime = 0; - tspeed = value & 3; - WRITE32LE(&ioMem[COMM_SIODATA32_L], 0xffffffff); - WRITE32LE(&ioMem[0x124], 0xffffffff); - } - } - } - value &= 0xff7f; - value |= (transfer != 0) << 7; + case MULTIPLAYER: { + bool start = (value & 0x80) && !linkid && !transfer && gba_link_enabled; + u16 si = value & 4; + // clear start, seqno, si (RO on slave, start = pulse on master) + value &= 0xff4b; + // get current si. This way, on slaves, it is low during xfer + if(linkid) { + if(!transfer) + value |= 4; + else + value |= READ16LE(&ioMem[COMM_SIOCNT]) & 4; } - value &= 0xff8b; - value |= (linkid ? 0xc : 8); - value |= linkid << 4; + if (start) { + if (lanlink.active) + { + if (lanlink.connected) + { + linkdata[0] = READ16LE(&ioMem[COMM_SIODATA8]); + savedlinktime = linktime; + tspeed = value & 3; + ls.Send(); + transfer = 1; + linktime = 0; + UPDATE_REG(COMM_SIOMULTI0, linkdata[0]); + UPDATE_REG(COMM_SIOMULTI1, 0xffff); + WRITE32LE(&ioMem[COMM_SIOMULTI2], 0xffffffff); + if (lanlink.speed&&oncewait == false) + ls.howmanytimes++; + after = false; + value &= ~0x40; + } else + value |= 0x40; // comm error + } + else if (linkmem->numgbas > 1) + { + // find first active attached GBA + // doing this first reduces the potential + // race window size for new connections + int n = linkmem->numgbas + 1; + int f = linkmem->linkflags; + int m; + do { + n--; + m = (1 << n) - 1; + } while((f & m) != m); + linkmem->trgbas = n; + + // before starting xfer, make pathetic attempt + // at clearing out any previous stuck xfer + // this will fail if a slave was stuck for + // too long + for(int i = 0; i < 4; i++) + while(WaitForSingleObject(linksync[i], 0) != WAIT_TIMEOUT); + + // transmit first value + linkmem->linkcmd = ('M' << 8) + (value & 3); + linkmem->linkdata[0] = READ16LE(&ioMem[COMM_SIODATA8]); + + // start up slaves & sync clocks + numtransfers = linkmem->numtransfers; + if (numtransfers != 0) + linkmem->lastlinktime = linktime; + else + linkmem->lastlinktime = 0; + + if ((++numtransfers) == 0) + linkmem->numtransfers = 2; + else + linkmem->numtransfers = numtransfers; + + transfer = 1; + linktime = 0; + tspeed = value & 3; + WRITE32LE(&ioMem[COMM_SIOMULTI0], 0xffffffff); + WRITE32LE(&ioMem[COMM_SIOMULTI2], 0xffffffff); + value &= ~0x40; + } + } + value |= (transfer != 0) << 7; + value |= (linkid && !transfer ? 0xc : 8); // set SD (high), SI (low on master) + value |= linkid << 4; // set seq UPDATE_REG(COMM_SIOCNT, value); if (linkid) - UPDATE_REG(COMM_RCNT, 7); + // SC low -> transfer in progress + // not sure why SO is low + UPDATE_REG(COMM_RCNT, transfer ? 6 : 7); else - UPDATE_REG(COMM_RCNT, 3); + // SI is always low on master + // SO, SC always low during transfer + // not sure why SO low otherwise + UPDATE_REG(COMM_RCNT, transfer ? 2 : 3); break; - + } case NORMAL8: case NORMAL32: case UART: @@ -195,8 +365,6 @@ void StartGPLink(u16 value) } } -#endif // _MSC_VER - void JoyBusConnect() { delete dol; @@ -270,16 +438,17 @@ void JoyBusUpdate(int ticks) } } -#ifdef _MSC_VER +static void ReInitLink(); -// Windows threading is within! void LinkUpdate(int ticks) { + // this actually gets called every single instruction, so keep default + // path as short as possible + linktime += ticks; if (rfu_enabled) { - linktime2 += ticks; // linktime2 is unused! rfu_transfer_end -= ticks; if (transfer && rfu_transfer_end <= 0) { @@ -325,7 +494,7 @@ void LinkUpdate(int ticks) linktime -= savedlinktime; } - if (transfer && linktime >= trtimeend[lanlink.numgbas-1][tspeed]) + if (transfer && linktime >= trtimeend[lanlink.numslaves-1][tspeed]) { if (READ16LE(&ioMem[COMM_SIOCNT]) & 0x4000) { @@ -335,7 +504,7 @@ void LinkUpdate(int ticks) UPDATE_REG(COMM_SIOCNT, (READ16LE(&ioMem[COMM_SIOCNT]) & 0xff0f) | (linkid << 4)); transfer = 0; - linktime -= trtimeend[lanlink.numgbas-1][tspeed]; + linktime -= trtimeend[lanlink.numslaves-1][tspeed]; oncewait = false; if (!lanlink.speed) @@ -345,19 +514,19 @@ void LinkUpdate(int ticks) else ls.Recv(); // WTF is the point of this? - UPDATE_REG(COMM_SIODATA32_H, linkdata[1]); - UPDATE_REG(0x124, linkdata[2]); - UPDATE_REG(0x126, linkdata[3]); + UPDATE_REG(COMM_SIOMULTI1, linkdata[1]); + UPDATE_REG(COMM_SIOMULTI2, linkdata[2]); + UPDATE_REG(COMM_SIOMULTI3, linkdata[3]); oncewait = true; } else { after = true; - if (lanlink.numgbas == 1) + if (lanlink.numslaves == 1) { - UPDATE_REG(COMM_SIODATA32_H, linkdata[1]); - UPDATE_REG(0x124, linkdata[2]); - UPDATE_REG(0x126, linkdata[3]); + UPDATE_REG(COMM_SIOMULTI1, linkdata[1]); + UPDATE_REG(COMM_SIOMULTI2, linkdata[2]); + UPDATE_REG(COMM_SIOMULTI3, linkdata[3]); } } } @@ -365,70 +534,143 @@ void LinkUpdate(int ticks) return; } - // ** CRASH ** linkmem is NULL, todo investigate why, added null check - if (linkid && !transfer && linkmem && linktime >= linkmem->lastlinktime && linkmem->numtransfers) + // slave startup depends on detecting change in numtransfers + // and syncing clock with master (after first transfer) + // this will fail if > ~2 minutes have passed since last transfer due + // to integer overflow + if(!transfer && numtransfers && linktime < 0) { + linktime = 0; + // there is a very, very, small chance that this will abort + // a transfer that was just started + linkmem->numtransfers = numtransfers = 0; + } + if (linkid && !transfer && linktime >= linkmem->lastlinktime && + linkmem->numtransfers != numtransfers) { - linkmem->linkdata[linkid] = READ16LE(&ioMem[COMM_SIODATA8]); + numtransfers = linkmem->numtransfers; + if(!numtransfers) + return; - if (linkmem->numtransfers == 1) - { - linktime = 0; - if (WaitForSingleObject(linksync[linkid], linktimeout) == WAIT_TIMEOUT) - linkmem->numtransfers = 0; + // if this or any previous machine was dropped, no transfer + // can take place + if(linkmem->trgbas <= linkid) { + transfer = 0; + numtransfers = 0; + // if this is the one that was dropped, reconnect + if(!(linkmem->linkflags & (1 << linkid))) + ReInitLink(); + return; } + + // sync clock + if (numtransfers == 1) + linktime = 0; else linktime -= linkmem->lastlinktime; - switch ((linkmem->linkcmd[0]) >> 8) + // there's really no point to this switch; 'M' is the only + // possible command. +#if 0 + switch ((linkmem->linkcmd) >> 8) { case 'M': - tspeed = (linkmem->linkcmd[0]) & 3; +#endif + tspeed = linkmem->linkcmd & 3; transfer = 1; - WRITE32LE(&ioMem[COMM_SIODATA32_L], 0xffffffff); - WRITE32LE(&ioMem[0x124], 0xffffffff); - UPDATE_REG(COMM_SIOCNT, READ16LE(&ioMem[COMM_SIOCNT]) | 0x80); + WRITE32LE(&ioMem[COMM_SIOMULTI0], 0xffffffff); + WRITE32LE(&ioMem[COMM_SIOMULTI2], 0xffffffff); + UPDATE_REG(COMM_SIOCNT, READ16LE(&ioMem[COMM_SIOCNT]) & ~0x40 | 0x80); +#if 0 break; } +#endif } if (!transfer) return; - if (transfer && linktime >= trtimedata[transfer-1][tspeed] && transfer <= linkmem->numgbas) + if (transfer <= linkmem->trgbas && linktime >= trtimedata[transfer-1][tspeed]) { - if (transfer-linkid == 2) - { - SetEvent(linksync[linkid+1]); - if (WaitForSingleObject(linksync[linkid], linktimeout) == WAIT_TIMEOUT) - linkmem->numtransfers = 0; - ResetEvent(linksync[linkid]); + // transfer #n -> wait for value n - 1 + if(transfer > 1 && linkid != transfer - 1) { + if(WaitForSingleObject(linksync[transfer - 1], linktimeout) == WAIT_TIMEOUT) { + // assume slave has dropped off if timed out + if(!linkid) { + linkmem->trgbas = transfer - 1; + int f = linkmem->linkflags; + f &= ~(1 << (transfer - 1)); + linkmem->linkflags = f; + if(f < (1 << transfer) - 1) + linkmem->numgbas = transfer - 1; + char message[30]; + sprintf(message, _("Player %d disconnected."), transfer - 1); + systemScreenMessage(message); + } + transfer = linkmem->trgbas + 1; + // next cycle, transfer will finish up + return; + } + } + // now that value is available, store it + UPDATE_REG((COMM_SIOMULTI0 - 2) + (transfer<<1), linkmem->linkdata[transfer-1]); + + // transfer machine's value at start of its transfer cycle + if(linkid == transfer) { + // skip if dropped + if(linkmem->trgbas <= linkid) { + transfer = 0; + numtransfers = 0; + // if this is the one that was dropped, reconnect + if(!(linkmem->linkflags & (1 << linkid))) + ReInitLink(); + return; + } + // SI becomes low + UPDATE_REG(COMM_SIOCNT, READ16LE(&ioMem[COMM_SIOCNT]) & ~4); + UPDATE_REG(COMM_RCNT, 10); + linkmem->linkdata[linkid] = READ16LE(&ioMem[COMM_SIODATA8]); + ReleaseSemaphore(linksync[linkid], linkmem->numgbas-1, NULL); + } + if(linkid == transfer - 1) { + // SO becomes low to begin next trasnfer + // may need to set DDR as well + UPDATE_REG(COMM_RCNT, 0x22); } - UPDATE_REG(0x11e + (transfer<<1), linkmem->linkdata[transfer-1]); + // next cycle transfer++; } - if (transfer && linktime >= trtimeend[linkmem->numgbas-2][tspeed]) + if (transfer > linkmem->trgbas && linktime >= trtimeend[transfer-3][tspeed]) { - if (linkid == linkmem->numgbas-1) - { - SetEvent(linksync[0]); - if (WaitForSingleObject(linksync[linkid], linktimeout) == WAIT_TIMEOUT) - linkmem->numtransfers = 0; - - ResetEvent(linksync[linkid]); - - } - + // wait for slaves to finish + // this keeps unfinished slaves from screwing up last xfer + // not strictly necessary; may just slow things down + if(!linkid) { + for(int i = 2; i < transfer; i++) + if(WaitForSingleObject(linksync[0], linktimeout) == WAIT_TIMEOUT) { + // impossible to determine which slave died + // so leave them alone for now + systemScreenMessage(_("Unknown slave timed out; resetting comm")); + linkmem->numtransfers = numtransfers = 0; + break; + } + } else if(linkmem->trgbas > linkid) + // signal master that this slave is finished + ReleaseSemaphore(linksync[0], 1, NULL); + linktime -= trtimeend[transfer - 3][tspeed]; transfer = 0; - linktime -= trtimeend[0][tspeed]; - if (READ16LE(&ioMem[COMM_SIOCNT]) & 0x4000) + u16 value = READ16LE(&ioMem[COMM_SIOCNT]); + if(!linkid) + value |= 4; // SI becomes high on slaves after xfer + UPDATE_REG(COMM_SIOCNT, (value & 0xff0f) | (linkid << 4)); + // SC/SI high after transfer + UPDATE_REG(COMM_RCNT, linkid ? 15 : 11); + if (value & 0x4000) { IF |= 0x80; UPDATE_REG(0x202, IF); } - UPDATE_REG(COMM_SIOCNT, (READ16LE(&ioMem[COMM_SIOCNT]) & 0xff0f) | (linkid << 4)); - linkmem->linkdata[linkid] = 0xffff; } return; @@ -528,7 +770,7 @@ u16 StartRFU(u16 value) numtransfers = 0; rfu_cmd |= 0x80; if (linkmem->numgbas == 2) - SetEvent(linksync[1-vbaid]); + ReleaseSemaphore(linksync[1-vbaid], 1, NULL); break; case 0x11: // ? always receives 0xff - I suspect it's something for 3+ players @@ -556,9 +798,8 @@ u16 StartRFU(u16 value) if((numtransfers++)==0) linktime = 1; linkmem->rfu_linktime[vbaid] = linktime; if(linkmem->numgbas==2){ - SetEvent(linksync[1-vbaid]); + ReleaseSemaphore(linksync[1-vbaid], 1, NULL); WaitForSingleObject(linksync[vbaid], linktimeout); - ResetEvent(linksync[vbaid]); } rfu_cmd |= 0x80; linktime = 0; @@ -595,9 +836,8 @@ u16 StartRFU(u16 value) linkmem->rfu_linktime[vbaid] = linktime; if (linkmem->numgbas == 2) { if (!linkid || (linkid && numtransfers)) - SetEvent(linksync[1-vbaid]); + ReleaseSemaphore(linksync[1-vbaid], 1, NULL); WaitForSingleObject(linksync[vbaid], linktimeout); - ResetEvent(linksync[vbaid]); } if ( linkid > 0) { memcpy(rfu_masterdata, linkmem->rfu_data[1-vbaid], 128); @@ -718,32 +958,16 @@ u16 StartRFU(u16 value) ////////////////////////////////////////////////////////////////////////// // Probably from here down needs to be replaced with SFML goodness :) +// tjm: what SFML goodness? SFML for network, yes, but not for IPC -int InitLink() +bool InitLink() { - WSADATA wsadata; - BOOL disable = true; - linkid = 0; - if(WSAStartup(MAKEWORD(1,1), &wsadata)!=0){ - WSACleanup(); - return 0; - } - - if((lanlink.tcpsocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET){ - MessageBox(NULL, "Couldn't create socket.", "Error!", MB_OK); - WSACleanup(); - return 0; - } - - setsockopt(lanlink.tcpsocket, IPPROTO_TCP, TCP_NODELAY, (char*)&disable, sizeof(BOOL)); - - if((mmf=CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(LINKDATA), "VBA link memory"))==NULL){ - closesocket(lanlink.tcpsocket); - WSACleanup(); - MessageBox(NULL, "Error creating file mapping", "Error", MB_OK|MB_ICONEXCLAMATION); - return 0; +#ifdef __WIN32__ + if((mmf=CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(LINKDATA), LOCAL_LINK_NAME))==NULL){ + systemMessage(0, N_("Error creating file mapping")); + return false; } if(GetLastError() == ERROR_ALREADY_EXISTS) @@ -751,81 +975,121 @@ int InitLink() else vbaid = 0; + if((linkmem=(LINKDATA *)MapViewOfFile(mmf, FILE_MAP_WRITE, 0, 0, sizeof(LINKDATA)))==NULL){ - closesocket(lanlink.tcpsocket); - WSACleanup(); CloseHandle(mmf); - MessageBox(NULL, "Error mapping file", "Error", MB_OK|MB_ICONEXCLAMATION); - return 0; + systemMessage(0, N_("Error mapping file")); + return false; } - - if(linkmem->linkflags&LINK_PARENTLOST) +#else + if((mmf = shm_open("/" LOCAL_LINK_NAME, O_RDWR|O_CREAT|O_EXCL, 0777)) < 0) { + vbaid = 1; + mmf = shm_open("/" LOCAL_LINK_NAME, O_RDWR, 0); + } else vbaid = 0; - - if(vbaid==0){ - linkid = 0; - if(linkmem->linkflags&LINK_PARENTLOST){ - linkmem->numgbas++; - linkmem->linkflags &= ~LINK_PARENTLOST; + if(mmf < 0 || ftruncate(mmf, sizeof(LINKDATA)) < 0 || + !(linkmem = (LINKDATA *)mmap(NULL, sizeof(LINKDATA), + PROT_READ|PROT_WRITE, MAP_SHARED, + mmf, 0))) { + systemMessage(0, N_("Error creating file mapping")); + if(mmf) { + if(!vbaid) + shm_unlink("/" LOCAL_LINK_NAME); + close(mmf); } - else - linkmem->numgbas=1; + } +#endif - for(i=0;i<4;i++){ - linkevent[15]=(char)i+'1'; - if((linksync[i]=CreateEvent(NULL, true, false, linkevent))==NULL){ - closesocket(lanlink.tcpsocket); - WSACleanup(); - UnmapViewOfFile(linkmem); - CloseHandle(mmf); - for(j=0;jlinkflags = 1; + linkmem->numgbas = 1; + linkmem->numtransfers=0; + for(i=0;i<4;i++) + linkmem->linkdata[i] = 0xffff; } else { - vbaid=linkmem->numgbas; - linkid = vbaid; - linkmem->numgbas++; - - if(linkmem->numgbas>4){ - linkmem->numgbas=4; - closesocket(lanlink.tcpsocket); - WSACleanup(); - MessageBox(NULL, "5 or more GBAs not supported.", "Error!", MB_OK|MB_ICONEXCLAMATION); + // FIXME: this should be done while linkmem is locked + // (no xfer in progress, no other vba trying to connect) + int n = linkmem->numgbas; + int f = linkmem->linkflags; + for(int i = 0; i <= n; i++) + if(!(f & (1 << i))) { + vbaid = i; + break; + } + if(vbaid == 4){ +#ifdef __WIN32__ UnmapViewOfFile(linkmem); CloseHandle(mmf); - return 0; - } - for(i=0;i<4;i++){ - linkevent[15]=(char)i+'1'; - if((linksync[i]=OpenEvent(EVENT_ALL_ACCESS, false, linkevent))==NULL){ - closesocket(lanlink.tcpsocket); - WSACleanup(); - CloseHandle(mmf); - UnmapViewOfFile(linkmem); - for(j=0;jnumgbas = n + 1; + linkmem->linkflags = f | (1 << vbaid); } + linkid = vbaid; - linkmem->lastlinktime=0xffffffff; - linkmem->numtransfers=0; - linkmem->linkflags=0; - lanlink.connected = false; - lanlink.thread = NULL; - lanlink.speed = false; for(i=0;i<4;i++){ - linkmem->linkdata[i] = 0xffff; - linkdata[i] = 0xffff; + linkevent[sizeof(linkevent)-2]=(char)i+'1'; +#ifdef __WIN32__ + linksync[i] = firstone ? + CreateSemaphore(NULL, 0, 4, linkevent) : + OpenSemaphore(SEMAPHORE_ALL_ACCESS, false, linkevent); + if(linksync[i] == NULL) { + UnmapViewOfFile(linkmem); + CloseHandle(mmf); + for(j=0;jlinkflags; + int n = linkmem->numgbas; + if(f & (1 << linkid)) { + systemMessage(0, N_("Lost link; reinitialize to reconnect")); + return; + } + linkmem->linkflags |= 1 << linkid; + if(n < linkid + 1) + linkmem->numgbas = linkid + 1; + numtransfers = linkmem->numtransfers; + systemScreenMessage(_("Lost link; reconnected")); } void CloseLink(void){ @@ -834,66 +1098,91 @@ void CloseLink(void){ char outbuffer[4]; outbuffer[0] = 4; outbuffer[1] = -32; - if(lanlink.type==0) send(lanlink.tcpsocket, outbuffer, 4, 0); + if(lanlink.type==0) lanlink.tcpsocket.Send(outbuffer, 4); } else { char outbuffer[12]; int i; outbuffer[0] = 12; outbuffer[1] = -32; - for(i=1;i<=lanlink.numgbas;i++){ + for(i=1;i<=lanlink.numslaves;i++){ if(lanlink.type==0){ - send(ls.tcpsocket[i], outbuffer, 12, 0); + ls.tcpsocket[i].Send(outbuffer, 12); } - closesocket(ls.tcpsocket[i]); + ls.tcpsocket[i].Close(); } } } - linkmem->numgbas--; - if(!linkid&&linkmem->numgbas!=0) - linkmem->linkflags|=LINK_PARENTLOST; - CloseHandle(mmf); - UnmapViewOfFile(linkmem); + int f = linkmem->linkflags; + f &= ~(1 << linkid); + if(f & 0xf) { + linkmem->linkflags = f; + int n = linkmem->numgbas; + for(int i = 0; i < n; i--) + if(f <= (1 << (i + 1)) - 1) { + linkmem->numgbas = i + 1; + break; + } + } for(i=0;i<4;i++){ if(linksync[i]!=NULL){ - PulseEvent(linksync[i]); +#ifdef __WIN32__ + ReleaseSemaphore(linksync[i], 1, NULL); CloseHandle(linksync[i]); +#else + sem_close(linksync[i]); + if(!(f & 0xf)) { + linkevent[sizeof(linkevent)-2]=(char)i+'1'; + sem_unlink(linkevent); + } +#endif } } - regSetDwordValue("LAN", lanlink.active); - closesocket(lanlink.tcpsocket); - WSACleanup(); +#ifdef __WIN32__ + CloseHandle(mmf); + UnmapViewOfFile(linkmem); + + // FIXME: move to caller + // (but there are no callers, so why bother?) + //regSetDwordValue("LAN", lanlink.active); +#else + if(!(f & 0xf)) + shm_unlink("/" LOCAL_LINK_NAME); + munmap(linkmem, sizeof(LINKDATA)); + close(mmf); +#endif return; } +// call this to clean up crashed program's shared state +// or to use TCP on same machine (for testing) +// this may be necessary under MSW as well, but I wouldn't know how +void CleanLocalLink() +{ +#ifndef __WIN32__ + shm_unlink("/" LOCAL_LINK_NAME); + for(int i = 0; i < 4; i++) { + linkevent[sizeof(linkevent) - 2] = '1' + i; + sem_unlink(linkevent); + } +#endif +} + // Server lserver::lserver(void){ - intinbuffer = (int*)inbuffer; + intinbuffer = (s32*)inbuffer; u16inbuffer = (u16*)inbuffer; - intoutbuffer = (int*)outbuffer; + intoutbuffer = (s32*)outbuffer; u16outbuffer = (u16*)outbuffer; oncewait = false; } -int lserver::Init(void *serverdlg){ - SOCKADDR_IN info; - DWORD nothing; - char str[100]; - - info.sin_family = AF_INET; - info.sin_addr.S_un.S_addr = INADDR_ANY; - info.sin_port = htons(5738); - - if(bind(lanlink.tcpsocket, (LPSOCKADDR)&info, sizeof(SOCKADDR_IN))==SOCKET_ERROR){ - closesocket(lanlink.tcpsocket); - if((lanlink.tcpsocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))==INVALID_SOCKET) - return WSAGetLastError(); - if(bind(lanlink.tcpsocket, (LPSOCKADDR)&info, sizeof(SOCKADDR_IN))==SOCKET_ERROR) - return WSAGetLastError(); - } - - if(listen(lanlink.tcpsocket, lanlink.numgbas)==SOCKET_ERROR) - return WSAGetLastError(); +bool lserver::Init(ServerInfoDisplay *sid){ + // too bad Listen() doesn't take an address as well + // then again, old code used INADDR_ANY anyway + if(!lanlink.tcpsocket.Listen(IP_LINK_PORT)) + // Note: old code closed socket & retried once on bind failure + return false; // FIXME: error code? if(lanlink.thread!=NULL){ lanlink.terminate = true; @@ -903,64 +1192,62 @@ int lserver::Init(void *serverdlg){ lanlink.terminate = false; linkid = 0; - gethostname(str, 100); - ((ServerWait*)serverdlg)->m_serveraddress.Format("Server IP address is: %s", inet_ntoa(*(LPIN_ADDR)(gethostbyname(str)->h_addr_list[0]))); + // should probably use GetPublicAddress() + sid->ShowServerIP(sf::IPAddress::GetLocalAddress()); - lanlink.thread = CreateThread(NULL, 0, LinkServerThread, serverdlg, 0, ¬hing); + lanlink.thread = new sf::Thread(LinkServerThread, sid); + lanlink.thread->Launch(); - return 0; + return true; } -DWORD WINAPI LinkServerThread(void *serverdlg){ - fd_set fdset; - timeval wsocktimeout; +void LinkServerThread(void *_sid){ + ServerInfoDisplay *sid = (ServerInfoDisplay *)_sid; + sf::Selector fdset; char inbuffer[256], outbuffer[256]; - int *intinbuffer = (int*)inbuffer; + s32 *intinbuffer = (s32*)inbuffer; u16 *u16inbuffer = (u16*)inbuffer; - int *intoutbuffer = (int*)outbuffer; + s32 *intoutbuffer = (s32*)outbuffer; u16 *u16outbuffer = (u16*)outbuffer; - BOOL disable = true; - wsocktimeout.tv_sec = 1; - wsocktimeout.tv_usec = 0; i = 0; - while(im_plconn[i].Format("Player %d connected", i+1); - ((ServerWait*)serverdlg)->UpdateData(false); i++; + WRITE16LE(&u16outbuffer[0], i); + WRITE16LE(&u16outbuffer[1], lanlink.numslaves); + ls.tcpsocket[i].Send(outbuffer, 4); + sid->ShowConnect(i); } } - ((ServerWait*)serverdlg)->m_prgctrl.StepIt(); - } - MessageBox(NULL, "All players connected", "Link", MB_OK); - ((ServerWait*)serverdlg)->SendMessage(WM_CLOSE, 0, 0); - - for(i=1;i<=lanlink.numgbas;i++){ - outbuffer[0] = 4; - send(ls.tcpsocket[i], outbuffer, 4, 0); + sid->Ping(); } lanlink.connected = true; - return 0; + sid->Connected(); + + for(i=1;i<=lanlink.numslaves;i++){ + outbuffer[0] = 4; + ls.tcpsocket[i].Send(outbuffer, 4); + } + + return; } void lserver::Send(void){ @@ -968,38 +1255,39 @@ void lserver::Send(void){ if(savedlinktime==-1){ outbuffer[0] = 4; outbuffer[1] = -32; //0xe0 - for(i=1;i<=lanlink.numgbas;i++){ - send(tcpsocket[i], outbuffer, 4, 0); - recv(tcpsocket[i], inbuffer, 4, 0); + for(i=1;i<=lanlink.numslaves;i++){ + tcpsocket[i].Send(outbuffer, 4); + size_t nr; + tcpsocket[i].Receive(inbuffer, 4, nr); } } outbuffer[1] = tspeed; - u16outbuffer[1] = linkdata[0]; - intoutbuffer[1] = savedlinktime; - if(lanlink.numgbas==1){ + WRITE16LE(&u16outbuffer[1], linkdata[0]); + WRITE32LE(&intoutbuffer[1], savedlinktime); + if(lanlink.numslaves==1){ if(lanlink.type==0){ outbuffer[0] = 8; - send(tcpsocket[1], outbuffer, 8, 0); + tcpsocket[1].Send(outbuffer, 8); } } - else if(lanlink.numgbas==2){ - u16outbuffer[4] = linkdata[2]; + else if(lanlink.numslaves==2){ + WRITE16LE(&u16outbuffer[4], linkdata[2]); if(lanlink.type==0){ outbuffer[0] = 10; - send(tcpsocket[1], outbuffer, 10, 0); - u16outbuffer[4] = linkdata[1]; - send(tcpsocket[2], outbuffer, 10, 0); + tcpsocket[1].Send(outbuffer, 10); + WRITE16LE(&u16outbuffer[4], linkdata[1]); + tcpsocket[2].Send(outbuffer, 10); } } else { if(lanlink.type==0){ outbuffer[0] = 12; - u16outbuffer[4] = linkdata[2]; - u16outbuffer[5] = linkdata[3]; - send(tcpsocket[1], outbuffer, 12, 0); - u16outbuffer[4] = linkdata[1]; - send(tcpsocket[2], outbuffer, 12, 0); - u16outbuffer[5] = linkdata[2]; - send(tcpsocket[3], outbuffer, 12, 0); + WRITE16LE(&u16outbuffer[4], linkdata[2]); + WRITE16LE(&u16outbuffer[5], linkdata[3]); + tcpsocket[1].Send(outbuffer, 12); + WRITE16LE(&u16outbuffer[4], linkdata[1]); + tcpsocket[2].Send(outbuffer, 12); + WRITE16LE(&u16outbuffer[5], linkdata[2]); + tcpsocket[3].Send(outbuffer, 12); } } } @@ -1009,35 +1297,38 @@ void lserver::Send(void){ void lserver::Recv(void){ int numbytes; if(lanlink.type==0){ // TCP - wsocktimeout.tv_usec = 0; - wsocktimeout.tv_sec = linktimeout / 1000; - fdset.fd_count = lanlink.numgbas; - for(i=0;i1) memcpy(inbuffer, inbuffer+inbuffer[0]*(howmanytimes-1), inbuffer[0]); + while(numbytes1) memmove(inbuffer, inbuffer+inbuffer[0]*(howmanytimes-1), inbuffer[0]); if(inbuffer[1]==-32){ char message[30]; lanlink.connected = false; - sprintf(message, "Player %d disconnected.", i+2); - MessageBox(NULL, message, "Link", MB_OK); + sprintf(message, _("Player %d disconnected."), i+2); + systemScreenMessage(message); outbuffer[0] = 4; outbuffer[1] = -32; - for(i=1;ih_addr_list); - - if(ioctlsocket(lanlink.tcpsocket, FIONBIO, ¬block)==SOCKET_ERROR) - return WSAGetLastError(); +bool lclient::Init(sf::IPAddress addr, ClientInfoDisplay *cid){ + serveraddr = addr; + serverport = IP_LINK_PORT; + lanlink.tcpsocket.SetBlocking(false); if(lanlink.thread!=NULL){ lanlink.terminate = true; @@ -1074,80 +1359,99 @@ int lclient::Init(LPHOSTENT hostentry, void *waitdlg){ lanlink.thread = NULL; } - ((ServerWait*)waitdlg)->SetWindowText("Connecting..."); + cid->ConnectStart(addr); lanlink.terminate = false; - lanlink.thread = CreateThread(NULL, 0, LinkClientThread, waitdlg, 0, ¬hing); + lanlink.thread = new sf::Thread(LinkClientThread, cid); + lanlink.thread->Launch(); return 0; } -DWORD WINAPI LinkClientThread(void *waitdlg){ - fd_set fdset; - timeval wsocktimeout; +void LinkClientThread(void *_cid){ + ClientInfoDisplay *cid = (ClientInfoDisplay *)_cid; + sf::Selector fdset; int numbytes; char inbuffer[16]; u16 *u16inbuffer = (u16*)inbuffer; unsigned long block = 0; - if(connect(lanlink.tcpsocket, (LPSOCKADDR)&lc.serverinfo, sizeof(SOCKADDR_IN))==SOCKET_ERROR){ - if(WSAGetLastError()!=WSAEWOULDBLOCK){ - MessageBox(NULL, "Couldn't connect to server.", "Link", MB_OK); - return 1; - } - wsocktimeout.tv_sec = 1; - wsocktimeout.tv_usec = 0; - do{ - if(lanlink.terminate) return 0; - fdset.fd_count = 1; - fdset.fd_array[0] = lanlink.tcpsocket; - ((ServerWait*)waitdlg)->m_prgctrl.StepIt(); - } while(select(0, NULL, &fdset, NULL, &wsocktimeout)!=1&&connect(lanlink.tcpsocket, (LPSOCKADDR)&lc.serverinfo, sizeof(SOCKADDR_IN))!=0); + while(lanlink.tcpsocket.Connect(lc.serverport, lc.serveraddr) != sf::Socket::Done) { + // stupid SFML has no way of giving what sort of error occurred + // so we'll just have to do a retry loop, I guess. + cid->Ping(); + if(lanlink.terminate) return; + // old code had broken sleep on socket, which isn't + // even connected yet + // corrected sleep on socket worked, but this is more sane + // and probably less portable... works with mingw32 at least + usleep(100000); } - ioctlsocket(lanlink.tcpsocket, FIONBIO, &block); - numbytes = 0; - while(numbytes<4) - numbytes += recv(lanlink.tcpsocket, inbuffer+numbytes, 16, 0); - linkid = (int)u16inbuffer[0]; - lanlink.numgbas = (int)u16inbuffer[1]; + size_t got; + while(numbytes<4) { + lanlink.tcpsocket.Receive(inbuffer+numbytes, 4 - numbytes, got); + numbytes += got; + fdset.Clear(); + fdset.Add(lanlink.tcpsocket); + fdset.Wait(0.1); + cid->Ping(); + if(lanlink.terminate) { + lanlink.tcpsocket.Close(); + return; + } + } + linkid = (int)READ16LE(&u16inbuffer[0]); + lanlink.numslaves = (int)READ16LE(&u16inbuffer[1]); - ((ServerWait*)waitdlg)->m_serveraddress.Format("Connected as #%d", linkid+1); - if(lanlink.numgbas!=linkid) ((ServerWait*)waitdlg)->m_plconn[0].Format("Waiting for %d players to join", lanlink.numgbas-linkid); - else ((ServerWait*)waitdlg)->m_plconn[0].Format("All players joined."); + cid->ShowConnect(linkid + 1, lanlink.numslaves - linkid); numbytes = 0; inbuffer[0] = 1; - while(numbytesSendMessage(WM_CLOSE, 0, 0); - - block = 1; - - ioctlsocket(lanlink.tcpsocket, FIONBIO, &block); + while(numbytesPing(); + if(lanlink.terminate) { + lanlink.tcpsocket.Close(); + return; + } + } lanlink.connected = true; - return 0; + + cid->Connected(); + + return; } void lclient::CheckConn(void){ - if((numbytes=recv(lanlink.tcpsocket, inbuffer, 256, 0))>0){ - while(numbytes0){ + while(numbytes -#endif +// register definitions; these are always present -#define LINK_PARENTLOST 0x80 #define UNSUPPORTED -1 #define MULTIPLAYER 0 #define NORMAL8 1 @@ -12,6 +12,7 @@ #define UART 3 #define JOYBUS 4 #define GP 5 + #define RFU_INIT 0 #define RFU_COMM 1 #define RFU_SEND 2 @@ -21,6 +22,11 @@ #define COMM_SIODATA32_H 0x122 #define COMM_SIOCNT 0x128 #define COMM_SIODATA8 0x12a +#define COMM_SIOMLT_SEND 0x12a +#define COMM_SIOMULTI0 0x120 +#define COMM_SIOMULTI1 0x122 +#define COMM_SIOMULTI2 0x124 +#define COMM_SIOMULTI3 0x126 #define COMM_RCNT 0x134 #define COMM_JOYCNT 0x140 #define COMM_JOY_RECV_L 0x150 @@ -45,13 +51,27 @@ enum JOY_CMD_WRITE = 0x15 }; -#ifdef _MSC_VER +// Link implementation; only present if enabled +#ifndef NO_LINK +#include +#include + +class ServerInfoDisplay +{ +public: + virtual void ShowServerIP(sf::IPAddress addr) = 0; + virtual void ShowConnect(int player) = 0; + virtual void Ping() = 0; + virtual void Connected() = 0; +}; + typedef struct { - u16 linkdata[4]; - u16 linkcmd[4]; + u16 linkdata[5]; + u16 linkcmd; u16 numtransfers; int lastlinktime; u8 numgbas; + u8 trgbas; u8 linkflags; int rfu_q[4]; u8 rfu_request[4]; @@ -62,72 +82,77 @@ typedef struct { class lserver{ int numbytes; - fd_set fdset; - timeval wsocktimeout; + sf::Selector fdset; //timeval udptimeout; char inbuffer[256], outbuffer[256]; - int *intinbuffer; + s32 *intinbuffer; u16 *u16inbuffer; - int *intoutbuffer; + s32 *intoutbuffer; u16 *u16outbuffer; int counter; int done; public: int howmanytimes; - SOCKET tcpsocket[4]; - SOCKADDR_IN udpaddr[4]; + sf::SocketTCP tcpsocket[4]; + sf::IPAddress udpaddr[4]; lserver(void); - int Init(void*); + bool Init(ServerInfoDisplay *); void Send(void); void Recv(void); }; +class ClientInfoDisplay { +public: + virtual void ConnectStart(sf::IPAddress addr) = 0; + virtual void Ping() = 0; + virtual void ShowConnect(int player, int togo) = 0; + virtual void Connected() = 0; +}; + class lclient{ - fd_set fdset; - timeval wsocktimeout; + sf::Selector fdset; char inbuffer[256], outbuffer[256]; - int *intinbuffer; + s32 *intinbuffer; u16 *u16inbuffer; - int *intoutbuffer; + s32 *intoutbuffer; u16 *u16outbuffer; int numbytes; public: bool oncesend; - SOCKADDR_IN serverinfo; - SOCKET noblock; + sf::IPAddress serveraddr; + unsigned short serverport; + sf::SocketTCP noblock; int numtransfers; lclient(void); - int Init(LPHOSTENT, void*); + bool Init(sf::IPAddress, ClientInfoDisplay *); void Send(void); void Recv(void); void CheckConn(void); }; typedef struct { - SOCKET tcpsocket; - //SOCKET udpsocket; - int numgbas; - HANDLE thread; - u8 type; - u8 server; + sf::SocketTCP tcpsocket; + //sf::SocketUDP udpsocket; + int numslaves; + sf::Thread *thread; + int type; + bool server; bool terminate; bool connected; bool speed; bool active; } LANLINKDATA; -#endif extern bool gba_joybus_enabled; -#ifndef NO_LINK extern sf::IPAddress joybusHostAddr; -#endif extern void JoyBusConnect(); extern void JoyBusShutdown(); extern void JoyBusUpdate(int ticks); extern bool gba_link_enabled; -#ifdef _MSC_VER +extern bool InitLink(); +extern void CloseLink(); extern void StartLink(u16); extern void StartGPLink(u16); extern void LinkSSend(u16); @@ -135,16 +160,28 @@ extern void LinkUpdate(int); extern void LinkChildStop(); extern void LinkChildSend(u16); extern void CloseLanLink(); -extern char *MakeInstanceFilename(const char *Input); +extern void CleanLocalLink(); +extern const char *MakeInstanceFilename(const char *Input); extern LANLINKDATA lanlink; extern int vbaid; extern bool rfu_enabled; extern int linktimeout; extern lclient lc; +extern lserver ls; extern int linkid; -#else // These are stubbed for now -inline void StartLink(u16){} -inline void StartGPLink(u16){} -inline void LinkSSend(u16){} -inline void LinkUpdate(int){} + +#else + +// stubs to keep #ifdef's out of mainline +#define StartLink(x) +#define StartGPLink(x) +#define LinkSSend(x) +#define LinkUpdate(x) +#define JoyBusUpdate(x) +#define InitLink() false +#define CloseLink() +#define gba_link_enabled false +#define gba_joybus_enabled false #endif + +#endif /* GBA_GBALINK_H */ diff --git a/src/gba/remote.cpp b/src/gba/remote.cpp index d023b16a..0dc58856 100644 --- a/src/gba/remote.cpp +++ b/src/gba/remote.cpp @@ -234,7 +234,8 @@ void remotePutPacket(const char *packet) char c = 0; while(c != '+'){ remoteSendFnc(buffer, (int)count + 4); - remoteRecvFnc(&c, 1); + if(remoteRecvFnc(&c, 1) < 0) + return; // fprintf(stderr,"sent:%s recieved:%c\n",buffer,c); } } @@ -571,7 +572,8 @@ void remoteStubMain() #endif debugger = false; break; - } + } else if(res == -2) + break; if(res < 1024){ buffer[res] = 0; }else{ diff --git a/src/gtk/system.cpp b/src/gtk/system.cpp index 39663a85..543ce3dd 100644 --- a/src/gtk/system.cpp +++ b/src/gtk/system.cpp @@ -175,3 +175,18 @@ void debuggerBreakOnWrite(u32 address, u32 oldvalue, u32 value, int size, int t) void (*dbgMain)() = debuggerMain; void (*dbgSignal)(int, int) = debuggerSignal; void (*dbgOutput)(const char *, u32) = debuggerOutput; + +void log(const char *defaultMsg, ...) +{ + static FILE *out = NULL; + + if(out == NULL) { + out = fopen("trace.log","w"); + } + + va_list valist; + + va_start(valist, defaultMsg); + vfprintf(out, defaultMsg, valist); + va_end(valist); +} diff --git a/src/sdl/SDL.cpp b/src/sdl/SDL.cpp index 31744bb1..2c7562cc 100644 --- a/src/sdl/SDL.cpp +++ b/src/sdl/SDL.cpp @@ -2697,3 +2697,18 @@ void systemOnSoundShutdown() void systemOnWriteDataToSoundBuffer(const u16 * finalWave, int length) { } + +void log(const char *defaultMsg, ...) +{ + static FILE *out = NULL; + + if(out == NULL) { + out = fopen("trace.log","w"); + } + + va_list valist; + + va_start(valist, defaultMsg); + vfprintf(out, defaultMsg, valist); + va_end(valist); +} diff --git a/src/win32/LinkOptions.cpp b/src/win32/LinkOptions.cpp index 46faa7e3..93ded40f 100644 --- a/src/win32/LinkOptions.cpp +++ b/src/win32/LinkOptions.cpp @@ -195,7 +195,7 @@ BOOL LinkServer::OnInitDialog() { CDialog::OnInitDialog(); - m_numplayers = lanlink.numgbas; + m_numplayers = lanlink.numslaves; m_prottype = lanlink.type; m_speed = lanlink.speed; @@ -490,6 +490,25 @@ void LinkOptions::OnCancel() return; } +class Win32ServerInfoDisplay : public ServerInfoDisplay +{ + Win32ServerInfoDisplay(ServerWait _dlg) : dlg(_dlg) {} + void ShowServerIP(sf::IPAddress addr) { + dlg->m_serveraddress.Format("Server IP address is: %s", addr.ToString); + } + void ShowConnect(int player) { + dlg->m_plconn[i].Format("Player %d connected", player); + dlg->UpdateData(false); + } + void Ping() { dlg->m_prgctrl.StepIt(); } + void Connected() { + MessageBox(NULL, "All players connected", "Link", MB_OK); + dlg->SendMessage(WM_CLOSE, 0, 0); + } +private: + ServerWait dlg; +} + void LinkServer::OnServerStart() { int errorcode; @@ -497,15 +516,15 @@ void LinkServer::OnServerStart() UpdateData(TRUE); - lanlink.numgbas = m_numplayers+1; + lanlink.numslaves = m_numplayers+1; lanlink.type = m_prottype; - lanlink.server = 1; + lanlink.server = true; lanlink.speed = m_speed==1 ? true : false; + sf::IPAddress addr; - if((errorcode=ls.Init(&dlg))!=0){ - char message[50]; - sprintf(message, "Error %d occured.\nPlease try again.", errorcode); - MessageBox(message, "Error", MB_OK); + Win32ServerInfoDisplay dlginfo(dlg); + if(!ls.Init(&dlginfo)){ + MessageBox("Error occured.\nPlease try again.", "Error", MB_OK); return; } @@ -526,6 +545,26 @@ BOOL LinkClient::OnInitDialog() return TRUE; } +class Win32ClientInfoDisplay : public ClientInfoDisplay +{ + Win32ClientInfoDisplay(ServerWait _dlg) : dlg(_dlg) {} + void ConnectStart(sf::IPAddress addr) { + dlg->SetWindowText("Connecting..."); + } + void ShowConnect(int player, int togo) { + dlg->m_serveraddress.Format("Connected as #%d", player); + if(togo) dlg->m_plconn[0].Format("Waiting for %d players to join", togo); + else dlg->m_plconn[0].Format("All players joined."); + } + void Ping() { dlg->m_prgctrl.StepIt(); } + void Connected() { + MessageBox(NULL, "Connected.", "Link", MB_OK); + dlg->SendMessage(WM_CLOSE, 0, 0); + } +private: + ServerWait dlg; +} + void LinkClient::OnLinkConnect() { char ipaddress[31]; @@ -535,12 +574,13 @@ void LinkClient::OnLinkConnect() UpdateData(TRUE); lanlink.type = m_prottype; - lanlink.server = 0; + lanlink.server = false; lanlink.speed = m_hacks==1 ? true : false; m_serverip.GetWindowText(ipaddress, 30); - if((errorcode=lc.Init(gethostbyname(ipaddress), &dlg))!=0){ + Win32ClientInfoDisplay dlginfo(dlg); + if((errorcode=lc.Init(sf::IPAddress(std::string(ipaddress)), &dlginfo))!=0){ char message[50]; sprintf(message, "Error %d occured.\nPlease try again.", errorcode); MessageBox(message, "Error", MB_OK); diff --git a/src/win32/rpi.h b/src/win32/rpi.h index f8b56d60..79201396 100644 --- a/src/win32/rpi.h +++ b/src/win32/rpi.h @@ -7,7 +7,11 @@ // http://www.hiend3d.com/hq2x.html // Modified by suanyuan //--------------------------------------------------------------------------------------------------------------------------- +#ifdef __MSW__ #include +#else +#define HMODULE void * +#endif //--------------------------------------------------------------------------------------------------------------------------- typedef struct @@ -54,10 +58,16 @@ typedef RENDER_PLUGIN_INFO *(*RENDPLUG_GetInfo)(void); #define RPI_565_SUPP 0x000000800 #define RPI_888_SUPP 0x000001000 +#define RPI_DST_WIDE 0x000008000 + +#define RPI_OUT_SCL1 0x000010000 #define RPI_OUT_SCL2 0x000020000 #define RPI_OUT_SCL3 0x000030000 #define RPI_OUT_SCL4 0x000040000 +#define RPI_OUT_SCLMSK 0x0000f0000 +#define RPI_OUT_SCLSH 16 + //--------------------------------------------------------------------------------------------------------------------------- int rpiScaleFactor(); diff --git a/src/wx/CMakeLists.txt b/src/wx/CMakeLists.txt new file mode 100644 index 00000000..9c9c691b --- /dev/null +++ b/src/wx/CMakeLists.txt @@ -0,0 +1,229 @@ +# This build is much easier if we just do it here. + +# not yet implemented +option( ENABLE_CAIRO "Enable Cairo rendering for the wxWidgets port" ON ) +if( WIN32 ) + # not yet implemented + option( ENABLE_DIRECT3D "Enable Direct3D rendering for the wxWidgets port" ON ) +endif( WIN32 ) +option( ENABLE_OPENAL "Enable OpenAL for the wxWidgets port" ON ) + +if( NOT ENABLE_CAIRO ) + ADD_DEFINITIONS (-DNO_CAIRO) +endif( NOT ENABLE_CAIRO ) + +if(NOT ENABLE_DIRECT3D) + ADD_DEFINITIONS(-DNO_D3D) +endif(NOT ENABLE_DIRECT3D) + +if(ENABLE_OPENAL) + FIND_PACKAGE(OpenAL REQUIRED) + INCLUDE_DIRECTORIES(${OPENAL_INCLUDE_DIR}) +else(ENABLE_OPENAL) + ADD_DEFINITIONS (-DNO_OAL) +endif(ENABLE_OPENAL) + + +# adv is for wxAboutBox +SET( wxWidgets_USE_LIBS core base xrc gl adv net ) +FIND_PACKAGE ( wxWidgets REQUIRED ) +EXECUTE_PROCESS(COMMAND sh "${wxWidgets_CONFIG_EXECUTABLE}" --cxxflags +OUTPUT_VARIABLE CXF) +INCLUDE( ${wxWidgets_USE_FILE} ) +FIND_PACKAGE ( Gettext REQUIRED ) +FIND_PROGRAM(XGETTEXT xgettext) +FIND_PROGRAM(MSGINIT msginit) +if(ENABLE_NLS AND (NOT XGETTEXT OR NOT MSGINIT)) + message(SEND_ERROR "Cannot find gettext ${XGETTEXT} ${MSGINIT}") +endif(ENABLE_NLS AND (NOT XGETTEXT OR NOT MSGINIT)) +IF(ENABLE_CAIRO) + FIND_PACKAGE ( PkgConfig REQUIRED ) + PKG_CHECK_MODULES(CAIRO REQUIRED cairo) + include_directories(${CAIRO_INCLUDE_DIRS}) + IF(WIN32) + # need gdiplus to extract hdc for cairo context + SET(CAIRO_LIBRARIES ${CAIRO_LIBRARIES} -lgdiplus) + ENDIF(WIN32) + # SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} ${CAIRO_CFLAGS}) +ELSE(ENABLE_CAIRO) + ADD_DEFINITIONS (-DNO_CAIRO) + SET(CAIRO_LIBRARIES ) +ENDIF(ENABLE_CAIRO) +IF(WIN32 AND ENABLE_DIRECTX) + FIND_PACKGE ( DirectX REQUIRED ) +ENDIF(WIN32 AND ENABLE_DIRECTX) + +# contrib widgets +include_directories(widgets) + +# for out-of-tree builds, grab includes from both target and source dirs +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +# external deps +SET(ICO_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../win32/res/VBA.ico) +SET(ICO_PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../gtk) +SET(WX_APP_ICON ${ICO_PARENT_DIR}/icons/32x32/apps/vbam.png) +#SET(WX_APP_ICON VBA_4_32x32x24.png) +SET(ICOX_FILES VBA_4_32x32x24.png) +#wxvbam.xrc now uses gvbam icon as well +#SET(ICOX_FILES ${ICOX_FILES} VBA_9_48x48x32.png) + +# Extract icons using icoutils (http://www.nongnu.org/icoutils/) +# Used for main prog. icon and about dialog (in xrc file) +# or, just use the icons already extracted for gtk +ADD_CUSTOM_COMMAND(OUTPUT ${ICOX_FILES} + COMMAND icotool -x ${ICO_FILE} + DEPENDS ${ICO_FILE}) + +# Convert to xpm using ImageMagick (http://www.imagemagick.org) +# not executed on win32 +IF( NOT WIN32 ) + FIND_PACKAGE(ImageMagick REQUIRED convert) + ADD_CUSTOM_COMMAND(OUTPUT wxvbam.xpm + COMMAND ${ImageMagick_convert_EXECUTABLE} ${WX_APP_ICON} wxvbam.xpm + # following is done using #define in wxvbam.cpp + # so there is no dependency on sed +# COMMAND sed -i 's/wxvbam\\[/wxvbam_xpm[/;s/char \\*/const char */' wxvbam.xpm + DEPENDS ${WX_APP_ICON}) +ENDIF( NOT WIN32 ) + +# wxrc does not support xrs files in -c output (> 10x compression) +# so do it manually using slow but portable bin2c.cmake script +SET(WX_XRC_ICON icons/32x32/apps/vbam.png) +ADD_CUSTOM_COMMAND(OUTPUT wxvbam.xrs + # doing this in its own dir prevents name prefixes + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/wxvbam.xrc wxvbam.xrc + COMMAND ${CMAKE_COMMAND} -E copy ${ICO_PARENT_DIR}/${WX_XRC_ICON} ${WX_XRC_ICON} + COMMAND wxrc wxvbam.xrc -o wxvbam.xrs + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${ICO_PARENT_DIR}/${XRC_ITEM} + DEPENDS wxvbam.xrc) +ADD_CUSTOM_COMMAND(OUTPUT builtin-xrc.h + COMMAND ${CMAKE_COMMAND} -DINFILE=wxvbam.xrs -DOUTFILE=builtin-xrc.h -DVARNAME=builtin_xrs -P ${CMAKE_CURRENT_SOURCE_DIR}/bin2c.cmake + DEPENDS wxvbam.xrs) + +# use a built-in vba-over.ini if no config file present +ADD_CUSTOM_COMMAND(OUTPUT builtin-over.h + COMMAND ${CMAKE_COMMAND} -DINFILE=${CMAKE_CURRENT_SOURCE_DIR}/../vba-over.ini -DOUTFILE=builtin-over.h -DVARNAME=builtin_over -P ${CMAKE_CURRENT_SOURCE_DIR}/bin2c.cmake + DEPENDS ../vba-over.ini) + + +# I don't like duplicating/triplicating code, so I only declare +# event handlers once, and copy them in other places they are needed +# all using portable cmake code +ADD_CUSTOM_COMMAND(OUTPUT cmdtab.cpp cmdhandlers.h cmd-evtable.h + COMMAND + ${CMAKE_COMMAND} -D OUTDIR=${CMAKE_CURRENT_BINARY_DIR} -P copy-events.cmake + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS cmdevents.cpp) + +# +# the following should be in the main file for consistency with +# other front ends, but can't due to cmake issues +# then again, the main file should be split up into separate dirs anyway +# + +SET( SRC_WX + wxvbam.cpp + guiinit.cpp + viewers.cpp + gfxviewers.cpp + cmdevents.cpp + opts.cpp + sys.cpp + panel.cpp + viewsupt.cpp + widgets/keyedit.cpp + widgets/joyedit.cpp + widgets/sdljoy.cpp + widgets/wxmisc.cpp + # common, but not in lib, apparently + ../common/SoundSDL.cpp + # probably ought to be in common + ../sdl/text.cpp + # from external source with minor modifications + widgets/checkedlistctrl.cpp + # generated + cmdtab.cpp + # generated includes must be explicitly listed + builtin-xrc.h + builtin-over.h + cmdhandlers.h + cmd-evtable.h +) + +IF(ENABLE_OPENAL) + SET( SRC_WX ${SRC_WX} openal.cpp ) +ENDIF(ENABLE_OPENAL) + +IF( WIN32 ) + SET( SRC_WX ${SRC_WX} wxvbam.rc dsound.cpp xaudio2.cpp ) + SET(DIRECTX_LIBRARIES -ldxguid -ldsound) + # not strictly directx, but win32-related + IF(ENABLE_DEBUGGER) + SET(DIRECTX_LIBRARIES ${DIRECTX_LIBRARIES} -lwsock32) + ENDIF(ENABLE_DEBUGGER) +ELSE( WIN32 ) + SET(DIRECTX_LIBRARIES ) + # generated file must be explicitly listed + SET( SRC_WX ${SRC_WX} wxvbam.xpm ) +ENDIF( WIN32 ) + +IF(APPLE) + # icon must be generated manually + SET( SRC_WX ${SRC_WX} wxvbam.icns ) + # png2icns is provided with libicns (http://icns.sourceforge.net/) + FIND_PROGRAM(PNG2ICNS png2icns) + # note: could add more icons, if available and proper size + SET(WX_APP_ICONS + ${WX_APP_ICON} + ${ICO_PARENT_DIR}/icons/16x16/apps/vbam.png) + ADD_CUSTOM_COMMAND(OUTPUT wxvbam.icns + COMMAND ${PNG2ICNS} wxvbam.icns ${WX_APP_ICONS}) + SET(MACOSX_BUNDLE_ICON_FILE wxvbam.icns) + SET_SOURCE_FILES_PROPERTIES(wxvbam.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) +ENDIF(APPLE) + +link_directories( ${CMAKE_BINARY_DIR} ) + +ADD_EXECUTABLE ( + wxvbam + WIN32 + MACOSX_BUNDLE + ${SRC_WX} +) + +TARGET_LINK_LIBRARIES ( + wxvbam + ${VBAMCORE_LIBS} + ${wxWidgets_LIBRARIES} + ${FFMPEG_LIBRARIES} + ${DIRECTX_LIBRARIES} + ${CAIRO_LIBRARIES} +) + +INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/wxvbam DESTINATION bin) +IF(NOT WIN32 AND NOT APPLE) + INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/wxvbam.desktop DESTINATION share/applications) + INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../gtk/icons/ DESTINATION share/icons/hicolor PATTERN ".svn" EXCLUDE) +ENDIF(NOT WIN32 AND NOT APPLE) + +# for consistency with others, copy exe to top-level dir +if(WIN32) + SET(WX_EXE_NAME wxvbam${CMAKE_EXECUTABLE_SUFFIX}) + ADD_CUSTOM_COMMAND(TARGET wxvbam POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${WX_EXE_NAME} ../../${WX_EXE_NAME}) +else(WIN32) +if(APPLE) + SET(WX_EXE_NAME wxvbam.app) + # this should set ROM file types correctly + SET_PROPERTY(TARGET wxvbam APPEND PROPERTY MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/wxplist.in) +else(APPLE) + SET(WX_EXE_NAME wxvbam${CMAKE_EXECUTABLE_SUFFIX}) +endif(APPLE) + ADD_CUSTOM_COMMAND(TARGET wxvbam POST_BUILD + # I'd rather make this link relative, but it's too hard + COMMAND rm -rf ../../${WX_EXE_NAME} + COMMAND ln -s ${CMAKE_CURRENT_BINARY_DIR}/${WX_EXE_NAME} ../../${WX_EXE_NAME}) +endif(WIN32) diff --git a/src/wx/bin2c.cmake b/src/wx/bin2c.cmake new file mode 100644 index 00000000..6aaa17e2 --- /dev/null +++ b/src/wx/bin2c.cmake @@ -0,0 +1,24 @@ +# portably convert binary file to header +FUNCTION(FILE2C INFILE VARNAME OUTFILE) + FILE(READ ${INFILE} HEXFILE HEX) + STRING(LENGTH ${HEXFILE} XRSLEN) + SET(HEXPOS 0) + FILE(WRITE ${OUTFILE} + "/* generated from ${INFILE}; do not edit */\n" + "const unsigned char ${VARNAME}[] = {") + WHILE(${HEXPOS} LESS ${XRSLEN}) + MATH(EXPR LPOS "${HEXPOS} % 32") + IF(NOT ${LPOS}) + FILE(APPEND ${OUTFILE} "\n") + ENDIF(NOT ${LPOS}) + STRING(SUBSTRING ${HEXFILE} ${HEXPOS} 2 HEXBYTE) + FILE(APPEND ${OUTFILE} "0x${HEXBYTE}") + MATH(EXPR HEXPOS "${HEXPOS} + 2") + IF(${HEXPOS} LESS ${XRSLEN}) + FILE(APPEND ${OUTFILE} ",") + ENDIF(${HEXPOS} LESS ${XRSLEN}) + ENDWHILE(${HEXPOS} LESS ${XRSLEN}) + FILE(APPEND ${OUTFILE} "};\n") +ENDFUNCTION(FILE2C) + +FILE2C(${INFILE} ${VARNAME} ${OUTFILE}) diff --git a/src/wx/cmdevents.cpp b/src/wx/cmdevents.cpp new file mode 100644 index 00000000..90fe5896 --- /dev/null +++ b/src/wx/cmdevents.cpp @@ -0,0 +1,2316 @@ +#ifndef NO_FFMPEG +#define __STDC_LIMIT_MACROS // required for ffmpeg +#define __STDC_CONSTANT_MACROS // required for ffmpeg +#endif +#include "wxvbam.h" +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef NO_FFMPEG +extern "C" { +#include +} +#endif +#include "../gb/gbPrinter.h" +#include "../gba/agbprint.h" + +#define GetXRCDialog(n) \ + wxStaticCast(FindWindow(XRCID(n)), wxDialog) + +bool cmditem_lt(const struct cmditem &cmd1, const struct cmditem &cmd2) +{ + return wxStrcmp(cmd1.cmd, cmd2.cmd) < 0; +} + +#define update_bcheck(s, f) do { \ + f = !f; \ + int id = XRCID(s); \ + for(int i = 0; i < checkable_mi.size(); i++) { \ + if(checkable_mi[i].cmd != id) \ + continue; \ + f = checkable_mi[i].mi->IsChecked(); \ + break; \ + } \ +} while(0) + +#define update_icheck(s, f, m, v) do { \ + bool is_checked = ((f) & (m)) != (v); \ + int id = XRCID(s); \ + for(int i = 0; i < checkable_mi.size(); i++) { \ + if(checkable_mi[i].cmd != id) \ + continue; \ + is_checked = checkable_mi[i].mi->IsChecked(); \ + break; \ + } \ + f = ((f) & ~(m)) | (is_checked ? (v) : 0); \ +} while(0) +#define update_icheck1(s, f, m) update_icheck(s, f, m, m) + +#define update_check(s, v) do { \ + int id = XRCID(s); \ + for(int i = 0; i < checkable_mi.size(); i++) { \ + if(checkable_mi[i].cmd != id) \ + continue; \ + checkable_mi[i].mi->Check(v); \ + break; \ + } \ +} while(0) + +//// File menu + +// formerly OpenGBA, OpenGBC, OpenGB +// having just one means separate ROM dirs only make sense on the cmd line + +static int open_ft = 0; +static wxString open_dir; + +EVT_HANDLER(wxID_OPEN, "Open ROM...") +{ + if(!open_dir.size()) + open_dir = gopts.gba_rom_dir; + // FIXME: ignore if non-existent or not a dir + wxString pats = _("GameBoy Advance Files (*.agb;*.gba;*.bin;*.elf;*.mb)|" + "*.agb;*.gba;*.bin;*.elf;*.mb" + "*.agb.gz;*.gba.gz;*.bin.gz;*.elf.gz;*.mb.gz" + "*.agb.z;*.gba.z;*.bin.z;*.elf.z;*.mb.z" + "|GameBoy Files (*.dmg;*.gb;*.gbc;*.cgb;*.sgb)|" + "*.dmg;*.gb;*.gbc;*.cgb;*.sgb" + "*.dmg.gz;*.gb.gz;*.gbc.gz;*.cgb.gz;*.sgb.gz" + "*.dmg.z;*.gb.z;*.gbc.z;*.cgb.z;*.sgb.z" + "|Archives (*.zip;*.7z;*.rar)" + "|*.zip;*.7z;*.rar|"); + pats.append(wxALL_FILES); + wxFileDialog dlg(this, _("Open ROM file"), open_dir, wxT(""), + pats, + wxFD_OPEN|wxFD_FILE_MUST_EXIST); + dlg.SetFilterIndex(open_ft); + if(ShowModal(&dlg) == wxID_OK) + wxGetApp().pending_load = dlg.GetPath(); + open_ft = dlg.GetFilterIndex(); + open_dir = dlg.GetDirectory(); +} + +EVT_HANDLER(RecentReset, "Reset recent ROM list") +{ + // only save config if there were items to remove + if(gopts.recent->GetCount()) { + while(gopts.recent->GetCount()) + gopts.recent->RemoveFileFromHistory(0); + wxConfig *cfg = wxGetApp().cfg; + cfg->SetPath(wxT("/Recent")); + gopts.recent->Save(*cfg); + cfg->SetPath(wxT("/")); + cfg->Flush(); + } +} + +EVT_HANDLER(RecentFreeze, "Freeze recent ROM list (toggle)") +{ + update_bcheck("RecentFreeze", gopts.recent_freeze); + update_opts(); +} + +// following 10 should really be a single ranged handler +// former names: Recent01 .. Recent10 +EVT_HANDLER(wxID_FILE1, "Load recent ROM 1") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(0)); +} + +EVT_HANDLER(wxID_FILE2, "Load recent ROM 2") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(1)); +} + +EVT_HANDLER(wxID_FILE3, "Load recent ROM 3") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(2)); +} + +EVT_HANDLER(wxID_FILE4, "Load recent ROM 4") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(3)); +} + +EVT_HANDLER(wxID_FILE5, "Load recent ROM 5") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(4)); +} + +EVT_HANDLER(wxID_FILE6, "Load recent ROM 6") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(5)); +} + +EVT_HANDLER(wxID_FILE7, "Load recent ROM 7") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(6)); +} + +EVT_HANDLER(wxID_FILE8, "Load recent ROM 8") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(7)); +} + +EVT_HANDLER(wxID_FILE9, "Load recent ROM 9") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(8)); +} + +EVT_HANDLER(wxID_FILE10, "Load recent ROM 10") +{ + panel->LoadGame(gopts.recent->GetHistoryFile(9)); +} + +static const struct rom_maker { + const wxChar *code, *name; +} makers[] = { + { wxT("01"), wxT("Nintendo") }, + { wxT("02"), wxT("Rocket Games") }, + { wxT("08"), wxT("Capcom") }, + { wxT("09"), wxT("Hot B Co.") }, + { wxT("0A"), wxT("Jaleco") }, + { wxT("0B"), wxT("Coconuts Japan") }, + { wxT("0C"), wxT("Coconuts Japan/G.X.Media") }, + { wxT("0H"), wxT("Starfish") }, + { wxT("0L"), wxT("Warashi Inc.") }, + { wxT("0N"), wxT("Nowpro") }, + { wxT("0P"), wxT("Game Village") }, + { wxT("13"), wxT("Electronic Arts Japan") }, + { wxT("18"), wxT("Hudson Soft Japan") }, + { wxT("19"), wxT("S.C.P.") }, + { wxT("1A"), wxT("Yonoman") }, + { wxT("1G"), wxT("SMDE") }, + { wxT("1P"), wxT("Creatures Inc.") }, + { wxT("1Q"), wxT("TDK Deep Impresion") }, + { wxT("20"), wxT("Destination Software") }, + { wxT("22"), wxT("VR 1 Japan") }, + { wxT("25"), wxT("San-X") }, + { wxT("28"), wxT("Kemco Japan") }, + { wxT("29"), wxT("Seta") }, + { wxT("2H"), wxT("Ubisoft Japan") }, + { wxT("2K"), wxT("NEC InterChannel") }, + { wxT("2L"), wxT("Tam") }, + { wxT("2M"), wxT("Jordan") }, + { wxT("2N"), wxT("Smilesoft") }, + { wxT("2Q"), wxT("Mediakite") }, + { wxT("36"), wxT("Codemasters") }, + { wxT("37"), wxT("GAGA Communications") }, + { wxT("38"), wxT("Laguna") }, + { wxT("39"), wxT("Telstar Fun and Games") }, + { wxT("41"), wxT("Ubi Soft Entertainment") }, + { wxT("42"), wxT("Sunsoft") }, + { wxT("47"), wxT("Spectrum Holobyte") }, + { wxT("49"), wxT("IREM") }, + { wxT("4D"), wxT("Malibu Games") }, + { wxT("4F"), wxT("Eidos/U.S. Gold") }, + { wxT("4J"), wxT("Fox Interactive") }, + { wxT("4K"), wxT("Time Warner Interactive") }, + { wxT("4Q"), wxT("Disney") }, + { wxT("4S"), wxT("Black Pearl") }, + { wxT("4X"), wxT("GT Interactive") }, + { wxT("4Y"), wxT("RARE") }, + { wxT("4Z"), wxT("Crave Entertainment") }, + { wxT("50"), wxT("Absolute Entertainment") }, + { wxT("51"), wxT("Acclaim") }, + { wxT("52"), wxT("Activision") }, + { wxT("53"), wxT("American Sammy Corp.") }, + { wxT("54"), wxT("Take 2 Interactive") }, + { wxT("55"), wxT("Hi Tech") }, + { wxT("56"), wxT("LJN LTD.") }, + { wxT("58"), wxT("Mattel") }, + { wxT("5A"), wxT("Mindscape/Red Orb Ent.") }, + { wxT("5C"), wxT("Taxan") }, + { wxT("5D"), wxT("Midway") }, + { wxT("5F"), wxT("American Softworks") }, + { wxT("5G"), wxT("Majesco Sales Inc") }, + { wxT("5H"), wxT("3DO") }, + { wxT("5K"), wxT("Hasbro") }, + { wxT("5L"), wxT("NewKidCo") }, + { wxT("5M"), wxT("Telegames") }, + { wxT("5N"), wxT("Metro3D") }, + { wxT("5P"), wxT("Vatical Entertainment") }, + { wxT("5Q"), wxT("LEGO Media") }, + { wxT("5S"), wxT("Xicat Interactive") }, + { wxT("5T"), wxT("Cryo Interactive") }, + { wxT("5W"), wxT("Red Storm Ent./BKN Ent.") }, + { wxT("5X"), wxT("Microids") }, + { wxT("5Z"), wxT("Conspiracy Entertainment Corp.") }, + { wxT("60"), wxT("Titus Interactive Studios") }, + { wxT("61"), wxT("Virgin Interactive") }, + { wxT("62"), wxT("Maxis") }, + { wxT("64"), wxT("LucasArts Entertainment") }, + { wxT("67"), wxT("Ocean") }, + { wxT("69"), wxT("Electronic Arts") }, + { wxT("6E"), wxT("Elite Systems Ltd.") }, + { wxT("6F"), wxT("Electro Brain") }, + { wxT("6G"), wxT("The Learning Company") }, + { wxT("6H"), wxT("BBC") }, + { wxT("6J"), wxT("Software 2000") }, + { wxT("6L"), wxT("BAM! Entertainment") }, + { wxT("6M"), wxT("Studio 3") }, + { wxT("6Q"), wxT("Classified Games") }, + { wxT("6S"), wxT("TDK Mediactive") }, + { wxT("6U"), wxT("DreamCatcher") }, + { wxT("6V"), wxT("JoWood Productions") }, + { wxT("6W"), wxT("SEGA") }, + { wxT("6X"), wxT("Wannado Edition") }, + { wxT("6Y"), wxT("LSP") }, + { wxT("6Z"), wxT("ITE Media") }, + { wxT("70"), wxT("Infogrames") }, + { wxT("71"), wxT("Interplay") }, + { wxT("72"), wxT("JVC Musical Industries Inc") }, + { wxT("73"), wxT("Parker Brothers") }, + { wxT("75"), wxT("SCI") }, + { wxT("78"), wxT("THQ") }, + { wxT("79"), wxT("Accolade") }, + { wxT("7A"), wxT("Triffix Ent. Inc.") }, + { wxT("7C"), wxT("Microprose Software") }, + { wxT("7D"), wxT("Universal Interactive Studios") }, + { wxT("7F"), wxT("Kemco") }, + { wxT("7G"), wxT("Rage Software") }, + { wxT("7H"), wxT("Encore") }, + { wxT("7J"), wxT("Zoo") }, + { wxT("7K"), wxT("BVM") }, + { wxT("7L"), wxT("Simon & Schuster Interactive") }, + { wxT("7M"), wxT("Asmik Ace Entertainment Inc./AIA") }, + { wxT("7N"), wxT("Empire Interactive") }, + { wxT("7Q"), wxT("Jester Interactive") }, + { wxT("7T"), wxT("Scholastic") }, + { wxT("7U"), wxT("Ignition Entertainment") }, + { wxT("7W"), wxT("Stadlbauer") }, + { wxT("80"), wxT("Misawa") }, + { wxT("83"), wxT("LOZC") }, + { wxT("8B"), wxT("Bulletproof Software") }, + { wxT("8C"), wxT("Vic Tokai Inc.") }, + { wxT("8J"), wxT("General Entertainment") }, + { wxT("8N"), wxT("Success") }, + { wxT("8P"), wxT("SEGA Japan") }, + { wxT("91"), wxT("Chun Soft") }, + { wxT("92"), wxT("Video System") }, + { wxT("93"), wxT("BEC") }, + { wxT("96"), wxT("Yonezawa/S'pal") }, + { wxT("97"), wxT("Kaneko") }, + { wxT("99"), wxT("Victor Interactive Software") }, + { wxT("9A"), wxT("Nichibutsu/Nihon Bussan") }, + { wxT("9B"), wxT("Tecmo") }, + { wxT("9C"), wxT("Imagineer") }, + { wxT("9F"), wxT("Nova") }, + { wxT("9H"), wxT("Bottom Up") }, + { wxT("9L"), wxT("Hasbro Japan") }, + { wxT("9N"), wxT("Marvelous Entertainment") }, + { wxT("9P"), wxT("Keynet Inc.") }, + { wxT("9Q"), wxT("Hands-On Entertainment") }, + { wxT("A0"), wxT("Telenet") }, + { wxT("A1"), wxT("Hori") }, + { wxT("A4"), wxT("Konami") }, + { wxT("A6"), wxT("Kawada") }, + { wxT("A7"), wxT("Takara") }, + { wxT("A9"), wxT("Technos Japan Corp.") }, + { wxT("AA"), wxT("JVC") }, + { wxT("AC"), wxT("Toei Animation") }, + { wxT("AD"), wxT("Toho") }, + { wxT("AF"), wxT("Namco") }, + { wxT("AG"), wxT("Media Rings Corporation") }, + { wxT("AH"), wxT("J-Wing") }, + { wxT("AK"), wxT("KID") }, + { wxT("AL"), wxT("MediaFactory") }, + { wxT("AP"), wxT("Infogrames Hudson") }, + { wxT("AQ"), wxT("Kiratto. Ludic Inc") }, + { wxT("B0"), wxT("Acclaim Japan") }, + { wxT("B1"), wxT("ASCII") }, + { wxT("B2"), wxT("Bandai") }, + { wxT("B4"), wxT("Enix") }, + { wxT("B6"), wxT("HAL Laboratory") }, + { wxT("B7"), wxT("SNK") }, + { wxT("B9"), wxT("Pony Canyon Hanbai") }, + { wxT("BA"), wxT("Culture Brain") }, + { wxT("BB"), wxT("Sunsoft") }, + { wxT("BD"), wxT("Sony Imagesoft") }, + { wxT("BF"), wxT("Sammy") }, + { wxT("BG"), wxT("Magical") }, + { wxT("BJ"), wxT("Compile") }, + { wxT("BL"), wxT("MTO Inc.") }, + { wxT("BN"), wxT("Sunrise Interactive") }, + { wxT("BP"), wxT("Global A Entertainment") }, + { wxT("BQ"), wxT("Fuuki") }, + { wxT("C0"), wxT("Taito") }, + { wxT("C2"), wxT("Kemco") }, + { wxT("C3"), wxT("Square Soft") }, + { wxT("C5"), wxT("Data East") }, + { wxT("C6"), wxT("Tonkin House") }, + { wxT("C8"), wxT("Koei") }, + { wxT("CA"), wxT("Konami/Palcom/Ultra") }, + { wxT("CB"), wxT("Vapinc/NTVIC") }, + { wxT("CC"), wxT("Use Co.,Ltd.") }, + { wxT("CD"), wxT("Meldac") }, + { wxT("CE"), wxT("FCI/Pony Canyon") }, + { wxT("CF"), wxT("Angel") }, + { wxT("CM"), wxT("Konami Computer Entertainment Osaka") }, + { wxT("CP"), wxT("Enterbrain") }, + { wxT("D1"), wxT("Sofel") }, + { wxT("D2"), wxT("Quest") }, + { wxT("D3"), wxT("Sigma Enterprises") }, + { wxT("D4"), wxT("Ask Kodansa") }, + { wxT("D6"), wxT("Naxat") }, + { wxT("D7"), wxT("Copya System") }, + { wxT("D9"), wxT("Banpresto") }, + { wxT("DA"), wxT("TOMY") }, + { wxT("DB"), wxT("LJN Japan") }, + { wxT("DD"), wxT("NCS") }, + { wxT("DF"), wxT("Altron Corporation") }, + { wxT("DH"), wxT("Gaps Inc.") }, + { wxT("DN"), wxT("ELF") }, + { wxT("E2"), wxT("Yutaka") }, + { wxT("E3"), wxT("Varie") }, + { wxT("E5"), wxT("Epoch") }, + { wxT("E7"), wxT("Athena") }, + { wxT("E8"), wxT("Asmik Ace Entertainment Inc.") }, + { wxT("E9"), wxT("Natsume") }, + { wxT("EA"), wxT("King Records") }, + { wxT("EB"), wxT("Atlus") }, + { wxT("EC"), wxT("Epic/Sony Records") }, + { wxT("EE"), wxT("IGS") }, + { wxT("EL"), wxT("Spike") }, + { wxT("EM"), wxT("Konami Computer Entertainment Tokyo") }, + { wxT("EN"), wxT("Alphadream Corporation") }, + { wxT("F0"), wxT("A Wave") }, + { wxT("G1"), wxT("PCCW") }, + { wxT("G4"), wxT("KiKi Co Ltd") }, + { wxT("G5"), wxT("Open Sesame Inc.") }, + { wxT("G6"), wxT("Sims") }, + { wxT("G7"), wxT("Broccoli") }, + { wxT("G8"), wxT("Avex") }, + { wxT("G9"), wxT("D3 Publisher") }, + { wxT("GB"), wxT("Konami Computer Entertainment Japan") }, + { wxT("GD"), wxT("Square-Enix") }, + { wxT("HY"), wxT("Sachen") } +}; +#define num_makers (sizeof(makers)/sizeof(makers[0])) +static bool maker_lt(const rom_maker &r1, const rom_maker &r2) +{ + return wxStrcmp(r1.code, r2.code) < 0; +} + +EVT_HANDLER_MASK(RomInformation, "ROM information...", CMDEN_GB|CMDEN_GBA) +{ + wxString s; +#define setlab(id) do { \ + /* SetLabelText is not in 2.8 */ \ + s.Replace(wxT("&"), wxT("&&"), true); \ + XRCCTRL(*dlg, id, wxControl)->SetLabel(s); \ +} while(0) +#define setblab(id, b) do { \ + s.Printf(wxT("%02x"), (unsigned int)b); \ + setlab(id); \ +} while(0) +#define setblabs(id, b, ts) do { \ + s.Printf(wxT("%02x (%s)"), (unsigned int)b, ts); \ + setlab(id); \ +} while(0) +#define setlabs(id, ts, l) do { \ + s = wxString((const char *)&(ts), wxConvLibc, l); \ + setlab(id); \ +} while(0) + switch(panel->game_type()) { + case IMAGE_GB: + { + wxDialog *dlg = GetXRCDialog("GBROMInfo"); + setlabs("Title", gbRom[0x134], 15); + setblab("Color", gbRom[0x143]); + if(gbRom[0x14b] == 0x33) + s = wxString((const char *)&gbRom[0x144], wxConvUTF8, 2); + else + s.Printf(wxT("%02x"), gbRom[0x14b]); + setlab("MakerCode"); + const rom_maker m = { s.c_str() }, *rm; + rm = std::lower_bound(&makers[0], &makers[num_makers], m, maker_lt); + if(rm < &makers[num_makers] && !wxStrcmp(m.code, rm->code)) + s = rm->name; + else + s = _("Unknown"); + setlab("MakerName"); + setblab("UnitCode", gbRom[0x146]); + const wxChar *type; + switch(gbRom[0x147]) { + case 0x00: + type = _("ROM"); + break; + case 0x01: + type = _("ROM+MBC1"); + break; + case 0x02: + type = _("ROM+MBC1+RAM"); + break; + case 0x03: + type = _("ROM+MBC1+RAM+BATT"); + break; + case 0x05: + type = _("ROM+MBC2"); + break; + case 0x06: + type = _("ROM+MBC2+BATT"); + break; + case 0x0b: + type = _("ROM+MMM01"); + break; + case 0x0c: + type = _("ROM+MMM01+RAM"); + break; + case 0x0d: + type = _("ROM+MMM01+RAM+BATT"); + break; + case 0x0f: + type = _("ROM+MBC3+TIMER+BATT"); + break; + case 0x10: + type = _("ROM+MBC3+TIMER+RAM+BATT"); + break; + case 0x11: + type = _("ROM+MBC3"); + break; + case 0x12: + type = _("ROM+MBC3+RAM"); + break; + case 0x13: + type = _("ROM+MBC3+RAM+BATT"); + break; + case 0x19: + type = _("ROM+MBC5"); + break; + case 0x1a: + type = _("ROM+MBC5+RAM"); + break; + case 0x1b: + type = _("ROM+MBC5+RAM+BATT"); + break; + case 0x1c: + type = _("ROM+MBC5+RUMBLE"); + break; + case 0x1d: + type = _("ROM+MBC5+RUMBLE+RAM"); + break; + case 0x1e: + type = _("ROM+MBC5+RUMBLE+RAM+BATT"); + break; + case 0x22: + type = _("ROM+MBC7+BATT"); + break; + case 0x55: + type = _("GameGenie"); + break; + case 0x56: + type = _("GameShark V3.0"); + break; + case 0xfc: + type = _("ROM+POCKET CAMERA"); + break; + case 0xfd: + type = _("ROM+BANDAI TAMA5"); + break; + case 0xfe: + type = _("ROM+HuC-3"); + break; + case 0xff: + type = _("ROM+HuC-1"); + break; + default: + type = _("Unknown"); + } + setblabs("DeviceType", gbRom[0x147], type); + switch(gbRom[0x148]) { + case 0: + type = wxT("32K"); + break; + case 1: + type = wxT("64K"); + break; + case 2: + type = wxT("128K"); + break; + case 3: + type = wxT("256K"); + break; + case 4: + type = wxT("512K"); + break; + case 5: + type = wxT("1M"); + break; + case 6: + type = wxT("2M"); + break; + case 7: + type = wxT("4M"); + break; + default: + type = _("Unknown"); + } + setblabs("ROMSize", gbRom[0x148], type); + switch(gbRom[0x149]) { + case 0: + type = _("None"); + break; + case 1: + type = wxT("2K"); + break; + case 2: + type = wxT("8K"); + break; + case 3: + type = wxT("32K"); + break; + case 4: + type = wxT("128K"); + break; + case 5: + type = wxT("64K"); + break; + } + setblabs("RAMSize", gbRom[0x149], type); + setblab("DestCode", gbRom[0x14a]); + setblab("LicCode", gbRom[0x14b]); + setblab("Version", gbRom[0x14c]); + u8 crc = 25; + for(int i = 0x134; i < 0x14d; i++) + crc += gbRom[i]; + crc = 256 - crc; + s.Printf(wxT("%02x (%02x)"), crc, gbRom[0x14d]); + setlab("CRC"); + u16 crc16 = 0; + for(int i = 0; i < gbRomSize; i++) + crc16 += gbRom[i]; + crc16 -= gbRom[0x14e] + gbRom[0x14f]; + s.Printf(wxT("%04x (%04x)"), crc16, gbRom[0x14e]*256+gbRom[0x14f]); + setlab("Checksum"); + dlg->Fit(); + ShowModal(dlg); + } + break; + case IMAGE_GBA: + { + wxDialog *dlg = GetXRCDialog("GBAROMInfo"); + setlabs("Title", rom[0xa0], 12); + setlabs("GameCode", rom[0xac], 4); + setlabs("MakerCode", rom[0xb0], 2); + const rom_maker m = { s.c_str() }, *rm; + rm = std::lower_bound(&makers[0], &makers[num_makers], m, maker_lt); + if(rm < &makers[num_makers] && !wxStrcmp(m.code, rm->code)) + s = rm->name; + else + s = _("Unknown"); + setlab("MakerName"); + setblab("UnitCode", rom[0xb3]); + s.Printf(wxT("%02x"), (unsigned int)rom[0xb4]); + if(rom[0xb4] & 0x80) + s.append(wxT(" (DACS)")); + setlab("DeviceType"); + setblab("Version", rom[0xbc]); + u8 crc = 0x19; + for(int i = 0xa0; i < 0xbd; i++) + crc += rom[i]; + crc = -crc; + s.Printf(wxT("%02x (%02x)"), crc, rom[0xbd]); + setlab("CRC"); + dlg->Fit(); + ShowModal(dlg); + } + break; + } +} + +static wxString batimp_path; + +EVT_HANDLER_MASK(ImportBatteryFile, "Import battery file...", CMDEN_GB|CMDEN_GBA) +{ + if(!batimp_path.size()) + batimp_path = panel->bat_dir(); + wxFileDialog dlg(this, _("Select battery file"), batimp_path, wxEmptyString, + _("Battery file (*.sav)|*.sav|Flash save (*.dat)|*.dat"), wxFD_OPEN|wxFD_FILE_MUST_EXIST); + int ret = ShowModal(&dlg); + batimp_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxString fn = dlg.GetPath(); + ret = wxMessageBox(_("Importing a battery file will erase any saved games (permanently after the next write). Do you want to continue?"), + _("Confirm import"), wxYES_NO|wxICON_EXCLAMATION); + if(ret == wxYES) { + wxString msg; + if(panel->emusys->emuReadBattery(fn.mb_fn_str())) + msg.Printf(_("Loaded battery %s"), fn.c_str()); + else + msg.Printf(_("Error loading battery %s"), fn.c_str()); + systemScreenMessage(msg); + } +} + +EVT_HANDLER_MASK(ImportGamesharkCodeFile, "Import GameShark code file...", CMDEN_GB|CMDEN_GBA) +{ + static wxString path; + wxFileDialog dlg(this, _("Select code file"), path, wxEmptyString, + panel->game_type() == IMAGE_GBA ? + _("Gameshark Code File (*.spc;*.xpc)|*.spc;*.xpc"): + _("Gameshark Code File (*.gcf)|*.gcf"), + wxFD_OPEN|wxFD_FILE_MUST_EXIST); + int ret = ShowModal(&dlg); + path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxString fn = dlg.GetPath(); + ret = wxMessageBox(_("Importing a code file will replace any loaded cheats. Do you want to continue?"), + _("Confirm import"), wxYES_NO|wxICON_EXCLAMATION); + if(ret == wxYES) { + wxString msg; + bool res; + if(panel->game_type() == IMAGE_GB) + // FIXME: this routine will not work on big-endian systems + // if the underlying file format is little-endian + // (fix in gb/gbCheats.cpp) + res = gbCheatReadGSCodeFile(fn.mb_fn_str()); + else { + // need to select game first + wxFFile f(fn, wxT("rb")); + if(!f.IsOpened()) { + wxLogError(_("Cannot open file %s"), fn.c_str()); + return; + } + // FIXME: in my code, I assume file format is little-endian + // however, in core code, it is assumed to be native-endian + u32 len; + char buf[14]; + if(f.Read(&len, sizeof(len)) != sizeof(len) || + wxUINT32_SWAP_ON_BE(len) != 14 || + f.Read(buf, 14) != 14 || memcmp(buf, "SharkPortCODES", 14)) { + wxLogError(_("Unsupported code file %s"), fn.c_str()); + return; + } + f.Seek(0x1e); + if(f.Read(&len, sizeof(len)) != sizeof(len)) + len = 0; + u32 game = 0; + if(len > 1) { + wxDialog *seldlg = GetXRCDialog("CodeSelect"); + wxControlWithItems *lst = XRCCTRL(*seldlg, "CodeList", wxControlWithItems); + lst->Clear(); + while(len-- > 0) { + u32 slen; + if(f.Read(&slen, sizeof(slen)) != sizeof(slen) || + slen > 1024) // arbitrary upper bound + break; + char buf[slen]; + if(f.Read(buf, slen) != slen) + break; + lst->Append(wxString(buf, wxConvLibc, slen)); + u32 ncodes; + if(f.Read(&ncodes, sizeof(ncodes)) != sizeof(ncodes)) + break; + for(; ncodes > 0; ncodes--) { + if(f.Read(&slen, sizeof(slen)) != sizeof(slen)) + break; + f.Seek(slen, wxFromCurrent); + if(f.Read(&slen, sizeof(slen)) != sizeof(slen)) + break; + f.Seek(slen + 4, wxFromCurrent); + if(f.Read(&slen, sizeof(slen)) != sizeof(slen)) + break; + f.Seek(slen * 12, wxFromCurrent); + } + } + int sel = ShowModal(seldlg); + if(sel != wxID_OK) + return; + game = lst->GetSelection(); + if(game == wxNOT_FOUND) + game = 0; + } + bool v3 = fn.size() >= 4 && + wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".xpc"), false); + + // FIXME: this routine will not work on big-endian systems + // if the underlying file format is little-endian + // (fix in gba/Cheats.cpp) + res = cheatsImportGSACodeFile(fn.mb_fn_str(), game, v3); + } + if(res) + msg.Printf(_("Loaded code file %s"), fn.c_str()); + else + msg.Printf(_("Error loading code file %s"), fn.c_str()); + systemScreenMessage(msg); + } +} + +static wxString gss_path; + +EVT_HANDLER_MASK(ImportGamesharkActionReplaySnapshot, + "Import GameShark Action Replay snapshot...", CMDEN_GB|CMDEN_GBA) +{ + wxFileDialog dlg(this, _("Select snapshot file"), gss_path, wxEmptyString, + panel->game_type() == IMAGE_GBA ? + _("GS & PAC Snapshots (*.sps;*.xps)|*.sps;*.xps|GameShark SP Snapshots (*.gsv)|*.gsv"): + _("Gameboy Snapshot (*.gbs)|*.gbs"), + wxFD_OPEN|wxFD_FILE_MUST_EXIST); + int ret = ShowModal(&dlg); + gss_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxString fn = dlg.GetPath(); + ret = wxMessageBox(_("Importing a snapshot file will erase any saved games (permanently after the next write). Do you want to continue?"), + _("Confirm import"), wxYES_NO|wxICON_EXCLAMATION); + if(ret == wxYES) { + wxString msg; + bool res; + if(panel->game_type() == IMAGE_GB) + res = gbReadGSASnapshot(fn.mb_fn_str()); + else { + bool gsv = fn.size() >= 4 && + wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".gsv"), false); + if(gsv) + // FIXME: this will fail on big-endian machines if + // file format is little-endian + // fix in GBA.cpp + res = CPUReadGSASPSnapshot(fn.mb_fn_str()); + else + // FIXME: this will fail on big-endian machines if + // file format is little-endian + // fix in GBA.cpp + res = CPUReadGSASnapshot(fn.mb_fn_str()); + } + if(res) + msg.Printf(_("Loaded snapshot file %s"), fn.c_str()); + else + msg.Printf(_("Error loading snapshot file %s"), fn.c_str()); + systemScreenMessage(msg); + } +} + +EVT_HANDLER_MASK(ExportBatteryFile, "Export battery file...", CMDEN_GB|CMDEN_GBA) +{ + if(!batimp_path.size()) + batimp_path = panel->bat_dir(); + wxFileDialog dlg(this, _("Select battery file"), batimp_path, wxEmptyString, + _("Battery file (*.sav)|*.sav|Flash save (*.dat)|*.dat"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + int ret = ShowModal(&dlg); + batimp_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxString fn = dlg.GetPath(); + wxString msg; + if(panel->emusys->emuWriteBattery(fn.mb_fn_str())) + msg.Printf(_("Wrote battery %s"), fn.c_str()); + else + msg.Printf(_("Error writing battery %s"), fn.c_str()); + systemScreenMessage(msg); +} + +EVT_HANDLER_MASK(ExportGamesharkSnapshot, "Export GameShark snapshot...", CMDEN_GBA) +{ + if(eepromInUse) { + wxLogError(_("EEPROM saves cannot be exported")); + return; + } + wxString def_name = panel->game_name(); + def_name.append(wxT(".sps")); + wxFileDialog dlg(this, _("Select snapshot file"), gss_path, def_name, + _("Gameshark Snapshot (*.sps)|*.sps"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + int ret = ShowModal(&dlg); + gss_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxString fn = dlg.GetPath(); + wxDialog *infodlg = GetXRCDialog("ExportSPS"); + wxTextCtrl *tit = XRCCTRL(*infodlg, "Title", wxTextCtrl), + *dsc = XRCCTRL(*infodlg, "Description", wxTextCtrl), + *n = XRCCTRL(*infodlg, "Notes", wxTextCtrl); + tit->SetValue(wxString((const char *)&rom[0xa0], wxConvLibc, 12)); + dsc->SetValue(wxDateTime::Now().Format(wxT("%c"))); + n->SetValue(_("Exported from VisualBoyAdvance-M")); + if(ShowModal(infodlg) != wxID_OK) + return; + wxString msg; + + // FIXME: this will fail on big-endian machines if file format is + // little-endian + // fix in GBA.cpp + if(CPUWriteGSASnapshot(fn.mb_fn_str(), tit->GetValue().utf8_str(), + dsc->GetValue().utf8_str(), n->GetValue().utf8_str())) + msg.Printf(_("Saved snapshot file %s"), fn.c_str()); + else + msg.Printf(_("Error saving snapshot file %s"), fn.c_str()); + systemScreenMessage(msg); +} + +EVT_HANDLER_MASK(ScreenCapture, "Screen capture...", CMDEN_GB|CMDEN_GBA) +{ + static wxString scap_path; + if(!scap_path.size()) { + scap_path = gopts.scrshot_dir; + if(scap_path.size()) { + wxFileName sp(scap_path, wxEmptyString); + if(!sp.IsAbsolute()) + scap_path = panel->game_dir() + wxT('/') + gopts.scrshot_dir; + wxFileName::Mkdir(scap_path, 0777, wxPATH_MKDIR_FULL); + } + } + wxString def_name = panel->game_name(); + if(gopts.cap_format == 0) + def_name.append(wxT(".png")); + else + def_name.append(wxT(".bmp")); + wxFileDialog dlg(this, _("Select output file"), scap_path, def_name, + _("PNG images|*.png|BMP images|*.bmp"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + dlg.SetFilterIndex(gopts.cap_format); + int ret = ShowModal(&dlg); + scap_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxString fn = dlg.GetPath(); + int fmt = dlg.GetFilterIndex(); + if(fn.size() >= 4) { + if(wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".bmp"), false)) + fmt = 1; + else if(wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".png"), false)) + fmt = 0; + } + if(fmt == 0) + panel->emusys->emuWritePNG(fn.mb_fn_str()); + else + panel->emusys->emuWriteBMP(fn.mb_fn_str()); + wxString msg; + msg.Printf(_("Wrote snapshot %s"), fn.c_str()); + systemScreenMessage(msg); +} + +EVT_HANDLER_MASK(RecordSoundStartRecording, "Start sound recording...", CMDEN_NSREC) +{ +#ifndef NO_FFMPEG + static wxString sound_exts; + static int sound_extno; + static wxString sound_path; + + if(!sound_exts.size()) { + sound_extno = -1; + int extno; + AVOutputFormat *fmt; + for(fmt = NULL, extno = 0; (fmt = av_oformat_next(fmt)); ) { + if(!fmt->extensions) + continue; + if(fmt->audio_codec == CODEC_ID_NONE) + continue; + sound_exts.append(wxString(fmt->long_name ? fmt->long_name : fmt->name, wxConvLibc)); + sound_exts.append(_(" files (")); + wxString ext(fmt->extensions, wxConvLibc); + ext.Replace(wxT(","), wxT(";*.")); + ext.insert(0, wxT("*.")); + if(sound_extno < 0 && ext.find(wxT("*.wav")) != wxString::npos) + sound_extno = extno; + sound_exts.append(ext); + sound_exts.append(wxT(")|")); + sound_exts.append(ext); + sound_exts.append(wxT('|')); + extno++; + } + sound_exts.append(wxALL_FILES); + if(sound_extno < 0) + sound_extno = extno; + } + if(!sound_path.size()) { + if(!gopts.recording_dir.size()) + sound_path = panel->game_dir(); + else { + wxFileName sp(gopts.recording_dir, wxEmptyString); + if(sp.IsAbsolute()) + sound_path = gopts.recording_dir; + else + sound_path = panel->game_dir() + wxT('/') + gopts.recording_dir; + } + wxFileName::Mkdir(sound_path, 0777, wxPATH_MKDIR_FULL); + } + wxString def_name = panel->game_name(); + const wxChar *extoff = sound_exts.c_str(); + for(int i = 0; i < sound_extno; i++) { + extoff = wxStrchr(extoff, wxT('|')) + 1; + extoff = wxStrchr(extoff, wxT('|')) + 1; + } + extoff = wxStrchr(extoff, wxT('|')) + 2; // skip * + def_name += wxString(extoff, wxStrcspn(extoff, wxT(";|"))); + wxFileDialog dlg(this, _("Select output file"), sound_path, def_name, + sound_exts, wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + dlg.SetFilterIndex(sound_extno); + int ret = ShowModal(&dlg); + sound_extno = dlg.GetFilterIndex(); + sound_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + panel->StartSoundRecording(dlg.GetPath()); +#endif +} + +EVT_HANDLER_MASK(RecordSoundStopRecording, "Stop sound recording", CMDEN_SREC) +{ +#ifndef NO_FFMPEG + panel->StopSoundRecording(); +#endif +} + +EVT_HANDLER_MASK(RecordAVIStartRecording, "Start video recording...", CMDEN_NVREC) +{ +#ifndef NO_FFMPEG + static wxString vid_exts; + static int vid_extno; + static wxString vid_path; + + if(!vid_exts.size()) { + vid_extno = -1; + int extno; + AVOutputFormat *fmt; + for(fmt = NULL, extno = 0; (fmt = av_oformat_next(fmt)); ) { + if(!fmt->extensions) + continue; + if(fmt->video_codec == CODEC_ID_NONE) + continue; + vid_exts.append(wxString(fmt->long_name ? fmt->long_name : fmt->name, wxConvLibc)); + vid_exts.append(_(" files (")); + wxString ext(fmt->extensions, wxConvLibc); + ext.Replace(wxT(","), wxT(";*.")); + ext.insert(0, wxT("*.")); + if(vid_extno < 0 && ext.find(wxT("*.avi")) != wxString::npos) + vid_extno = extno; + vid_exts.append(ext); + vid_exts.append(wxT(")|")); + vid_exts.append(ext); + vid_exts.append(wxT('|')); + extno++; + } + vid_exts.append(wxALL_FILES); + if(vid_extno < 0) + vid_extno = extno; + } + if(!vid_path.size()) { + if(!gopts.recording_dir.size()) + vid_path = panel->game_dir(); + else { + wxFileName sp(gopts.recording_dir, wxEmptyString); + if(sp.IsAbsolute()) + vid_path = gopts.recording_dir; + else + vid_path = panel->game_dir() + wxT('/') + gopts.recording_dir; + } + wxFileName::Mkdir(vid_path, 0777, wxPATH_MKDIR_FULL); + } + wxString def_name = panel->game_name(); + const wxChar *extoff = vid_exts.c_str(); + for(int i = 0; i < vid_extno; i++) { + extoff = wxStrchr(extoff, wxT('|')) + 1; + extoff = wxStrchr(extoff, wxT('|')) + 1; + } + extoff = wxStrchr(extoff, wxT('|')) + 2; // skip * + def_name += wxString(extoff, wxStrcspn(extoff, wxT(";|"))); + wxFileDialog dlg(this, _("Select output file"), vid_path, def_name, + vid_exts, wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + dlg.SetFilterIndex(vid_extno); + int ret = ShowModal(&dlg); + vid_extno = dlg.GetFilterIndex(); + vid_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + panel->StartVidRecording(dlg.GetPath()); +#endif +} + +EVT_HANDLER_MASK(RecordAVIStopRecording, "Stop video recording", CMDEN_VREC) +{ +#ifndef NO_FFMPEG + panel->StopVidRecording(); +#endif +} + +static wxString mov_path; + +EVT_HANDLER_MASK(RecordMovieStartRecording, "Start game recording...", CMDEN_NGREC) +{ + if(!mov_path.size()) { + if(!gopts.recording_dir.size()) + mov_path = panel->game_dir(); + else { + wxFileName sp(gopts.recording_dir, wxEmptyString); + if(sp.IsAbsolute()) + mov_path = gopts.recording_dir; + else + mov_path = panel->game_dir() + wxT('/') + gopts.recording_dir; + } + wxFileName::Mkdir(mov_path, 0777, wxPATH_MKDIR_FULL); + } + wxString def_name = panel->game_name() + wxT(".vmv"); + wxFileDialog dlg(this, _("Select output file"), mov_path, def_name, + _("VBA Movie files|*.vmv"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + int ret = ShowModal(&dlg); + mov_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + systemStartGameRecording(dlg.GetPath()); +} + +EVT_HANDLER_MASK(RecordMovieStopRecording, "Stop game recording", CMDEN_GREC) +{ + systemStopGameRecording(); +} + +EVT_HANDLER_MASK(PlayMovieStartPlaying, "Start playing movie...", CMDEN_NGREC|CMDEN_NGPLAY) +{ + if(!mov_path.size()) { + if(!gopts.recording_dir.size()) + mov_path = panel->game_dir(); + else { + wxFileName sp(gopts.recording_dir, wxEmptyString); + if(sp.IsAbsolute()) + mov_path = gopts.recording_dir; + else + mov_path = panel->game_dir() + wxT('/') + gopts.recording_dir; + } + if(!wxFileName::DirExists(mov_path)) + mov_path = wxFileName::GetCwd(); + } + systemStopGamePlayback(); + wxString def_name = panel->game_name() + wxT(".vmv"); + wxFileDialog dlg(this, _("Select file"), mov_path, def_name, + _("VBA Movie files|*.vmv"), wxFD_OPEN|wxFD_FILE_MUST_EXIST); + int ret = ShowModal(&dlg); + mov_path = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + systemStartGamePlayback(dlg.GetPath()); +} + +EVT_HANDLER_MASK(PlayMovieStopPlaying, "Stop playing movie", CMDEN_GPLAY) +{ + systemStopGamePlayback(); +} + +// formerly Close +EVT_HANDLER_MASK(wxID_CLOSE, "Close", CMDEN_GB|CMDEN_GBA) +{ + panel->UnloadGame(); +} + +// formerly Exit +EVT_HANDLER(wxID_EXIT, "Exit") +{ + Close(false); +} + + +// Emulation menu +EVT_HANDLER(Pause, "Pause (toggle)") +{ + update_bcheck("Pause", paused); + if(paused) + panel->Pause(); + else if(!IsPaused()) + panel->Resume(); + // undo next-frame's zeroing of frameskip + int fs = panel->game_type() == IMAGE_GB ? gopts.gb_frameskip : gopts.gba_frameskip; + if(fs > 0) + systemFrameSkip = fs; +} + +// new +EVT_HANDLER_MASK(EmulatorSpeedupToggle, "Turbo mode (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_bcheck("EmulatorSpeedupToggle", turbo); +} + +EVT_HANDLER_MASK(Reset, "Reset", CMDEN_GB|CMDEN_GBA) +{ + panel->emusys->emuReset(); + // systemScreenMessage("Reset"); +} + +EVT_HANDLER(ToggleFullscreen, "Full screen (toggle)") +{ + panel->ShowFullScreen(!IsFullScreen()); +} + +EVT_HANDLER(JoypadAutofireA, "Autofire A (toggle)") +{ + update_icheck1("JoypadAutofireA", autofire, KEYM_A); +} + +EVT_HANDLER(JoypadAutofireB, "Autofire B (toggle)") +{ + update_icheck1("JoypadAutofireB", autofire, KEYM_B); +} + +EVT_HANDLER(JoypadAutofireL, "Autofire L (toggle)") +{ + update_icheck1("JoypadAutofireL", autofire, KEYM_LEFT); +} + +EVT_HANDLER(JoypadAutofireR, "Autofire R (toggle)") +{ + update_icheck1("JoypadAutofireR", autofire, KEYM_RIGHT); +} + +// new +EVT_HANDLER_MASK(LanLink, "Start LAN link", CMDEN_LINK_ANY) +{ +#ifndef NO_LINK + if(lanlink.connected) { + // while we could deactivate the command when connected, it is more + // user-friendly to display a message indidcating why + wxLogError(_("LAN link is already active. Disable link mode to disconnect.")); + return; + } + if(rfu_enabled) { + // see above comment + wxLogError(_("RFU is currently only supported in local mode.")); + return; + } + wxDialog *dlg = GetXRCDialog("NetLink"); + ShowModal(dlg); + panel->SetFrameTitle(); +#endif +} + +EVT_HANDLER_MASK(LoadGameRecent, "Load most recent save", CMDEN_SAVST) +{ + panel->LoadState(); +} + +EVT_HANDLER(LoadGameAutoLoad, "Auto load most recent save (toggle)") +{ + update_bcheck("LoadGameAutoLoad", gopts.autoload_state); + update_opts(); +} + +EVT_HANDLER_MASK(LoadGame01, "Load saved state 1", CMDEN_SAVST) +{ + panel->LoadState(1); +} + +EVT_HANDLER_MASK(LoadGame02, "Load saved state 2", CMDEN_SAVST) +{ + panel->LoadState(2); +} + +EVT_HANDLER_MASK(LoadGame03, "Load saved state 3", CMDEN_SAVST) +{ + panel->LoadState(3); +} + +EVT_HANDLER_MASK(LoadGame04, "Load saved state 4", CMDEN_SAVST) +{ + panel->LoadState(4); +} + +EVT_HANDLER_MASK(LoadGame05, "Load saved state 5", CMDEN_SAVST) +{ + panel->LoadState(5); +} + +EVT_HANDLER_MASK(LoadGame06, "Load saved state 6", CMDEN_SAVST) +{ + panel->LoadState(6); +} + +EVT_HANDLER_MASK(LoadGame07, "Load saved state 7", CMDEN_SAVST) +{ + panel->LoadState(7); +} + +EVT_HANDLER_MASK(LoadGame08, "Load saved state 8", CMDEN_SAVST) +{ + panel->LoadState(8); +} + +EVT_HANDLER_MASK(LoadGame09, "Load saved state 9", CMDEN_SAVST) +{ + panel->LoadState(9); +} + +EVT_HANDLER_MASK(LoadGame10, "Load saved state 10", CMDEN_SAVST) +{ + panel->LoadState(10); +} + +static wxString st_dir; + +EVT_HANDLER_MASK(Load, "Load state...", CMDEN_GB|CMDEN_GBA) +{ + if(st_dir.empty()) + st_dir = panel->state_dir(); + wxFileDialog dlg(this, _("Select state file"), st_dir, wxEmptyString, + _("VisualBoyAdvance saved game files|*.sgm"), wxFD_OPEN|wxFD_FILE_MUST_EXIST); + int ret = ShowModal(&dlg); + st_dir = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + panel->LoadState(dlg.GetPath()); +} + +// new +EVT_HANDLER(KeepSaves, "Do not load battery saves (toggle)") +{ + update_bcheck("KeepSaves", skipSaveGameBattery); + update_opts(); +} + +// new +EVT_HANDLER(KeepCheats, "Do not change cheat list (toggle)") +{ + update_bcheck("KeepCheats", skipSaveGameCheats); + update_opts(); +} + +EVT_HANDLER_MASK(SaveGameOldest, "Save state to oldest slot", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(); +} + +EVT_HANDLER_MASK(SaveGame01, "Save state 1", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(1); +} + +EVT_HANDLER_MASK(SaveGame02, "Save state 2", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(2); +} + +EVT_HANDLER_MASK(SaveGame03, "Save state 3", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(3); +} + +EVT_HANDLER_MASK(SaveGame04, "Save state 4", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(4); +} + +EVT_HANDLER_MASK(SaveGame05, "Save state 5", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(5); +} + +EVT_HANDLER_MASK(SaveGame06, "Save state 6", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(6); +} + +EVT_HANDLER_MASK(SaveGame07, "Save state 7", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(7); +} + +EVT_HANDLER_MASK(SaveGame08, "Save state 8", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(8); +} + +EVT_HANDLER_MASK(SaveGame09, "Save state 9", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(9); +} + +EVT_HANDLER_MASK(SaveGame10, "Save state 10", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(10); +} + +EVT_HANDLER_MASK(Save, "Save state as...", CMDEN_GB|CMDEN_GBA) +{ + if(st_dir.empty()) + st_dir = panel->state_dir(); + wxFileDialog dlg(this, _("Select state file"), st_dir, wxEmptyString, + _("VisualBoyAdvance saved game files|*.sgm"), wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + int ret = ShowModal(&dlg); + st_dir = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + panel->SaveState(dlg.GetPath()); +} + +static int state_slot = 0; + +// new +EVT_HANDLER_MASK(LoadGameSlot, "Load current state slot", CMDEN_GB|CMDEN_GBA) +{ + panel->LoadState(state_slot + 1); +} + +// new +EVT_HANDLER_MASK(SaveGameSlot, "Save current state slot", CMDEN_GB|CMDEN_GBA) +{ + panel->SaveState(state_slot + 1); +} + +// new +EVT_HANDLER_MASK(IncrGameSlot, "Increase state slot number", CMDEN_GB|CMDEN_GBA) +{ + state_slot = (state_slot + 1) % 10; +} + +// new +EVT_HANDLER_MASK(DecrGameSlot, "Decrease state slot number", CMDEN_GB|CMDEN_GBA) +{ + state_slot = (state_slot + 9) % 10; +} + +// new +EVT_HANDLER_MASK(IncrGameSlotSave, "Increase state slot number and save", CMDEN_GB|CMDEN_GBA) +{ + state_slot = (state_slot + 1) % 10; + panel->SaveState(state_slot + 1); +} + +EVT_HANDLER_MASK(Rewind, "Rewind", CMDEN_REWIND) +{ + MainFrame *mf = wxGetApp().frame; + GameArea *panel = mf->GetPanel(); + int rew_st = (panel->next_rewind_state + NUM_REWINDS - 1) % NUM_REWINDS; + // if within 5 seconds of last one, and > 1 state, delete last state & move back + // FIXME: 5 should actually be user-configurable + // maybe instead of 5, 10% of rewind_interval + if(panel->num_rewind_states > 1 && + (gopts.rewind_interval <= 5 || + panel->rewind_time / 6 > gopts.rewind_interval - 5)) { + --panel->num_rewind_states; + panel->next_rewind_state = rew_st; + if(gopts.rewind_interval > 5) + rew_st = (rew_st + NUM_REWINDS - 1) % NUM_REWINDS; + } + panel->emusys->emuReadMemState(&panel->rewind_mem[rew_st * REWIND_SIZE], + REWIND_SIZE); + InterframeCleanup(); + // FIXME: if(paused) blank screen + panel->do_rewind = false; + panel->rewind_time = gopts.rewind_interval * 6; +// systemScreenMessage(_("Rewinded")); +} + +EVT_HANDLER_MASK(CheatsList, "List cheats...", CMDEN_GB|CMDEN_GBA) +{ + wxDialog *dlg = GetXRCDialog("CheatList"); + ShowModal(dlg); +} + +EVT_HANDLER_MASK(CheatsSearch, "Create cheat...", CMDEN_GB|CMDEN_GBA) +{ + wxDialog *dlg = GetXRCDialog("CheatCreate"); + ShowModal(dlg); +} + +// new +EVT_HANDLER(CheatsAutoSaveLoad, "Auto save/load cheats (toggle)") +{ + update_bcheck("CheatsAutoSaveLoad", gopts.autoload_cheats); + update_opts(); +} + +// was CheatsDisable +// changed for convenience to match internal variable functionality +EVT_HANDLER(CheatsEnable, "Enable cheats (toggle)") +{ + update_bcheck("CheatsEnable", cheatsEnabled); + update_opts(); +} + + +// Debug menu +EVT_HANDLER_MASK(VideoLayersBG0, "Video layer BG0 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersBG0", layerSettings, (1<<8)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersBG1, "Video layer BG1 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersBG1", layerSettings, (1<<9)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersBG2, "Video layer BG2 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersBG2", layerSettings, (1<<10)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersBG3, "Video layer BG3 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersBG3", layerSettings, (1<<11)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersOBJ, "Video layer OBJ (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersOBJ", layerSettings, (1<<12)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersWIN0, "Video layer WIN0 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersWIN0", layerSettings, (1<<13)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersWIN1, "Video layer WIN1 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersWIN1", layerSettings, (1<<14)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(VideoLayersOBJWIN, "Video layer OBJWIN (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("VideoLayersOBJWIN", layerSettings, (1<<15)); + layerEnable = DISPCNT & layerSettings; + CPUUpdateRenderBuffers(false); +} + +// not in menu +EVT_HANDLER_MASK(VideoLayersReset, "Show all video layers", CMDEN_GB|CMDEN_GBA) +{ +#define set_vl(s) do { \ + int id = XRCID(s); \ + for(int i = 0; i < checkable_mi.size(); i++) \ + if(checkable_mi[i].cmd == id) { \ + checkable_mi[i].mi->Check(true); \ + break; \ + } \ +} while(0) + layerSettings = 0x7f00; + layerEnable = DISPCNT & layerSettings; + set_vl("VideoLayersBG0"); + set_vl("VideoLayersBG1"); + set_vl("VideoLayersBG2"); + set_vl("VideoLayersBG3"); + set_vl("VideoLayersOBJ"); + set_vl("VideoLayersWIN0"); + set_vl("VideoLayersWIN1"); + set_vl("VideoLayersOBJWIN"); + CPUUpdateRenderBuffers(false); +} + +EVT_HANDLER_MASK(SoundChannel1, "Sound Channel 1 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("SoundChannel1", gopts.sound_en, (1<<0)); + soundSetEnable(gopts.sound_en); + update_opts(); +} + +EVT_HANDLER_MASK(SoundChannel2, "Sound Channel 2 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("SoundChannel2", gopts.sound_en, (1<<1)); + soundSetEnable(gopts.sound_en); + update_opts(); +} + +EVT_HANDLER_MASK(SoundChannel3, "Sound Channel 3 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("SoundChannel3", gopts.sound_en, (1<<2)); + soundSetEnable(gopts.sound_en); + update_opts(); +} + +EVT_HANDLER_MASK(SoundChannel4, "Sound Channel 4 (toggle)", CMDEN_GB|CMDEN_GBA) +{ + update_icheck1("SoundChannel4", gopts.sound_en, (1<<3)); + soundSetEnable(gopts.sound_en); + update_opts(); +} + +EVT_HANDLER_MASK(DirectSoundA, "Direct Sound A (toggle)", CMDEN_GBA) +{ + update_icheck1("DirectSoundA", gopts.sound_en, (1<<8)); + soundSetEnable(gopts.sound_en); + update_opts(); +} + +EVT_HANDLER_MASK(DirectSoundB, "Direct Sound B (toggle)", CMDEN_GBA) +{ + update_icheck1("DirectSoundB", gopts.sound_en, (1<<9)); + soundSetEnable(gopts.sound_en); + update_opts(); +} + +EVT_HANDLER(ToggleSound, "Enable/disable all sound channels") +{ + bool en = gopts.sound_en == 0; + gopts.sound_en = en ? 0x30f : 0; + update_check("SoundChannel1", en); + update_check("SoundChannel2", en); + update_check("SoundChannel3", en); + update_check("SoundChannel4", en); + update_check("DirectSoundA", en); + update_check("DirectSoundB", en); + soundSetEnable(gopts.sound_en); + update_opts(); + systemScreenMessage(en ? _("Sound enabled") : _("Sound disabled")); +} + +EVT_HANDLER(IncreaseVolume, "Increase volume") +{ + gopts.sound_vol += 5; + if(gopts.sound_vol > 200) + gopts.sound_vol = 200; + update_opts(); + soundSetVolume((float)gopts.sound_vol / 100.0); + wxString msg; + msg.Printf(_("Volume: %d%%"), gopts.sound_vol); + systemScreenMessage(msg); +} + +EVT_HANDLER(DecreaseVolume, "Decrease volume") +{ + gopts.sound_vol -= 5; + if(gopts.sound_vol < 0) + gopts.sound_vol = 0; + update_opts(); + soundSetVolume((float)gopts.sound_vol / 100.0); + wxString msg; + msg.Printf(_("Volume: %d%%"), gopts.sound_vol); + systemScreenMessage(msg); +} + +EVT_HANDLER_MASK(NextFrame, "Next Frame", CMDEN_GB|CMDEN_GBA) +{ + update_check("Pause", true); + paused = true; + pause_next = true; + if(!IsPaused()) + panel->Resume(); + systemFrameSkip = 0; +} + +EVT_HANDLER_MASK(Disassemble, "Disassemble...", CMDEN_GB|CMDEN_GBA) +{ + Disassemble(); +} + +// only GBA generates the log messages this handles +// you could view them even w/o a gba cart, but why? +EVT_HANDLER_MASK(Logging, "Logging...", CMDEN_GBA) +{ + wxDialog *dlg = wxGetApp().frame->logdlg; + dlg->Show(); + dlg->Raise(); +} + +EVT_HANDLER_MASK(IOViewer, "I/O Viewer...", CMDEN_GBA) +{ + IOViewer(); +} + +EVT_HANDLER_MASK(MapViewer, "Map Viewer...", CMDEN_GB|CMDEN_GBA) +{ + MapViewer(); +} + +EVT_HANDLER_MASK(MemoryViewer, "Memory Viewer...", CMDEN_GB|CMDEN_GBA) +{ + MemViewer(); +} + +EVT_HANDLER_MASK(OAMViewer, "OAM Viewer...", CMDEN_GB|CMDEN_GBA) +{ + OAMViewer(); +} + +EVT_HANDLER_MASK(PaletteViewer, "Palette Viewer...", CMDEN_GB|CMDEN_GBA) +{ + PaletteViewer(); +} + +EVT_HANDLER_MASK(TileViewer, "Tile Viewer...", CMDEN_GB|CMDEN_GBA) +{ + TileViewer(); +} + +extern int remotePort; + +EVT_HANDLER_MASK(DebugGDB, "Wait for GDB connection...", CMDEN_NGDB_GBA) +{ + ModalPause mp; + int port = wxGetNumberFromUser( +#ifdef __WXMSW__ + wxEmptyString, +#else + _("Set to 0 for pseudo tty"), +#endif + _("Port to wait for connection:"), + _("GDB Connection"), remotePort, +#ifdef __WXMSW__ + 1025, +#else + 0, +#endif + 65535, this); + if(port < 0) + return; + remotePort = port; + wxString msg; +#ifndef __WXMSW__ + if(!port) { + if(!debugOpenPty()) + return; + msg.Printf(_("Waiting for connection at %s"), debugGetSlavePty().c_str()); + } else +#endif + { + if(!debugStartListen(port)) + return; + msg.Printf(_("Waiting for connection on port %d"), port); + } + wxProgressDialog dlg(_("Waiting for GDB..."), msg, 100, this, + wxPD_APP_MODAL|wxPD_CAN_ABORT|wxPD_ELAPSED_TIME); + bool connected = false; + while(dlg.Pulse()) { +#ifndef __WXMSW__ + if(!port) + connected = debugWaitPty(); + else +#endif + connected = debugWaitSocket(); + if(connected) + break; + // sleep a bit more in case of infinite loop + wxMilliSleep(10); + } + if(!connected) + remoteCleanUp(); + else { + debugger = true; + dbgMain = remoteStubMain; + dbgSignal = remoteStubSignal; + dbgOutput = remoteOutput; + cmd_enable &= ~(CMDEN_NGDB_ANY|CMDEN_NGDB_GBA); + cmd_enable |= CMDEN_GDB; + enable_menus(); + } +} + +EVT_HANDLER_MASK(DebugGDBLoad, "Load and wait for GDB...", CMDEN_NGDB_ANY) +{ + wxCommandEvent ev; + ModalPause mp; + OnwxID_OPEN(ev); + if(wxGetApp().pending_load.empty()) + return; + panel->UnloadGame(); + DoDebugGDB(); +} + +EVT_HANDLER_MASK(DebugGDBBreak, "Break into GDB", CMDEN_GDB) +{ + if(armState) { + armNextPC -= 4; + reg[15].I -= 4; + } else { + armNextPC -= 2; + reg[15].I -= 2; + } + debugger = true; +} + +EVT_HANDLER_MASK(DebugGDBDisconnect, "Disconnect GDB", CMDEN_GDB) +{ + debugger = false; + dbgMain = NULL; + dbgSignal = NULL; + dbgOutput = NULL; + remoteCleanUp(); + cmd_enable &= ~CMDEN_GDB; + cmd_enable |= CMDEN_NGDB_GBA|CMDEN_NGDB_ANY; + enable_menus(); +} + + +// Options menu +EVT_HANDLER(GeneralConfigure, "General options...") +{ + int rew = gopts.rewind_interval; + wxDialog *dlg = GetXRCDialog("GeneralConfig"); + if(ShowModal(dlg) == wxID_OK) + update_opts(); + if(panel->game_type() != IMAGE_UNKNOWN) + soundSetThrottle(gopts.throttle); + if(rew != gopts.rewind_interval) { + if(!gopts.rewind_interval) { + if(panel->num_rewind_states) { + cmd_enable &= ~CMDEN_REWIND; + enable_menus(); + } + panel->num_rewind_states = 0; + panel->do_rewind = false; + } else { + if(!panel->num_rewind_states) + panel->do_rewind = true; + panel->rewind_time = gopts.rewind_interval * 6; + } + } +} + +EVT_HANDLER(GameBoyConfigure, "Game Boy options...") +{ + wxDialog *dlg = GetXRCDialog("GameBoyConfig"); + wxChoice *c = XRCCTRL(*dlg, "Borders", wxChoice); + bool borderon = gbBorderOn; + bool printeron = gopts.gbprint; + if(!gbBorderOn && !gbBorderAutomatic) + c->SetSelection(0); + else if(gbBorderOn) + c->SetSelection(1); + else + c->SetSelection(2); + if(ShowModal(dlg) != wxID_OK) + return; + switch(c->GetSelection()) { + case 0: + gbBorderOn = gbBorderAutomatic = false; + break; + case 1: + gbBorderOn = true; + break; + case 2: + gbBorderOn = false; + gbBorderAutomatic = true; + break; + } + // this value might have been overwritten by FrameSkip + if(XRCCTRL(*dlg, "FrameSkipAuto", wxCheckBox)->GetValue()) + gopts.gb_frameskip = -1; + update_opts(); + if(panel->game_type() == IMAGE_GB) { + if(borderon != gbBorderOn) { + if(gbBorderOn) { + panel->AddBorder(); + gbSgbRenderBorder(); + } else + panel->DelBorder(); + } + // autoskip will self-adjust + if(gopts.gb_frameskip >= 0) + systemFrameSkip = gopts.gb_frameskip; + // don't want to have to reset to change colors + memcpy(gbPalette, &systemGbPalette[gbPaletteOption * 8], 8 * sizeof(systemGbPalette[0])); + } + if(printeron != gopts.gbprint) { + if(gopts.gbprint) + gbSerialFunction = gbPrinterSend; + else + gbSerialFunction = NULL; + } +} + +EVT_HANDLER(GameBoyAdvanceConfigure, "Game Boy Advance options...") +{ + wxDialog *dlg = GetXRCDialog("GameBoyAdvanceConfig"); + wxTextCtrl *ovcmt = XRCCTRL(*dlg, "Comment", wxTextCtrl); + wxString cmt; + wxChoice *ovrtc = XRCCTRL(*dlg, "OvRTC", wxChoice), + *ovst = XRCCTRL(*dlg, "OvSaveType", wxChoice), + *ovfs = XRCCTRL(*dlg, "OvFlashSize", wxChoice), + *ovmir = XRCCTRL(*dlg, "OvMirroring", wxChoice); + if(panel->game_type() == IMAGE_GBA) { + wxString s = wxString((const char *)&rom[0xac], wxConvLibc, 4); + XRCCTRL(*dlg, "GameCode", wxControl)->SetLabel(s); + cmt = wxString((const char *)&rom[0xa0], wxConvLibc, 12); + wxFileConfig *cfg = wxGetApp().overrides; + if(cfg->HasGroup(s)) { + cfg->SetPath(s); + cmt = cfg->Read(wxT("comment"), cmt); + ovcmt->SetValue(cmt); + ovrtc->SetSelection(cfg->Read(wxT("rtcEnabled"), -1) + 1); + ovst->SetSelection(cfg->Read(wxT("saveType"), -1) + 1); + ovfs->SetSelection((cfg->Read(wxT("flashSize"), -1) >> 17) + 1); + ovmir->SetSelection(cfg->Read(wxT("mirroringEnabled"), -1) + 1); + cfg->SetPath(wxT("/")); + } else { + ovcmt->SetValue(cmt); + ovrtc->SetSelection(0); + ovst->SetSelection(0); + ovfs->SetSelection(0); + ovmir->SetSelection(0); + } + } else { + XRCCTRL(*dlg, "GameCode", wxControl)->SetLabel(wxEmptyString); + ovcmt->SetValue(wxEmptyString); + ovrtc->SetSelection(0); + ovst->SetSelection(0); + ovfs->SetSelection(0); + ovmir->SetSelection(0); + } + if(ShowModal(dlg) != wxID_OK) + return; + // this value might have been overwritten by FrameSkip + if(XRCCTRL(*dlg, "FrameSkipAuto", wxCheckBox)->GetValue()) + gopts.gba_frameskip = -1; + update_opts(); + if(panel->game_type() == IMAGE_GBA) { + // autoskip will self-adjust + if(gopts.gba_frameskip >= 0) + systemFrameSkip = gopts.gba_frameskip; + agbPrintEnable(gopts.agbprint); +#if 0 // disabled in win32 version for undocumented "problems" + if(gopts.skip_intro) + *((u32 *)rom) = 0xea00002e; + else + *((u32 *)rom) = /* original value */; +#endif + wxString s = wxString((const char *)&rom[0xac], wxConvLibc, 4); + wxFileConfig *cfg = wxGetApp().overrides; + bool chg; + if(cfg->HasGroup(s)) { + cfg->SetPath(s); + chg = + ovcmt->GetValue() != cmt || + ovrtc->GetSelection() != cfg->Read(wxT("rtcEnabled"), -1) + 1 || + ovst->GetSelection() != cfg->Read(wxT("saveType"), -1) + 1 || + ovfs->GetSelection() != (cfg->Read(wxT("flashSize"), -1) >> 17) + 1 || + ovmir->GetSelection() != cfg->Read(wxT("mirroringEnabled"), -1) + 1; + cfg->SetPath(wxT("/")); + } else + chg = ovrtc->GetSelection() != 0 || ovst->GetSelection() != 0 || + ovfs->GetSelection() != 0 || ovmir->GetSelection() != 0; + if(chg) { + wxString vba_over; + wxFileName fn(wxStandardPaths::Get().GetUserDataDir(), wxT("vba-over.ini")); + if(fn.FileExists()) { + wxFileInputStream fis(fn.GetFullPath()); + wxStringOutputStream sos(&vba_over); + fis.Read(sos); + } + if(cfg->HasGroup(s)) { + cfg->SetPath(s); + if(cfg->Read(wxT("path"), wxEmptyString) == fn.GetPath()) { + // EOL can be either \n (unix), \r\n (dos), or \r (old mac) + wxString res(wxT("(^|[\n\r])" // a new line + "(" // capture group as \2 + "(#[^\n\r]*(\r?\n|\r))?" // an optional comment line + "\\[")); // the group header + res += s; + res += wxT( "\\]" + "([^[#]" // non-comment non-group-start chars + "|[^\r\n \t][ \t]*[[#]" // or comment/grp start chars in middle of line + "|#[^\n\r]*(\r?\n|\r)[^[]" // or comments not followed by grp start + ")*" + ")" // end of group + // no need to try to describe what's next + // as the regex should maximize match size + ); + wxRegEx re(res); + // there may be more than one group if it was hand-edited + // so remove them all + // could use re.Replace(), but this is more reliable + while(re.Matches(vba_over)) { + size_t beg, end; + re.GetMatch(&beg, &end, 2); + vba_over.erase(beg, end - beg); + } + } + cfg->SetPath(wxT("/")); + cfg->DeleteGroup(s); + } + cfg->SetPath(s); + cfg->Write(wxT("path"), fn.GetPath()); + cfg->Write(wxT("comment"), ovcmt->GetValue()); + vba_over.append(wxT("# ")); + vba_over.append(ovcmt->GetValue()); + vba_over.append(wxTextFile::GetEOL()); + vba_over.append(wxT('[')); + vba_over.append(s); + vba_over.append(wxT(']')); + vba_over.append(wxTextFile::GetEOL()); + int sel; +#define appendval(n) do { \ + vba_over.append(wxT(n)); \ + vba_over.append(wxT('=')); \ + vba_over.append((wxChar)(wxT('0') + sel - 1)); \ + vba_over.append(wxTextFile::GetEOL()); \ + cfg->Write(wxT(n), sel - 1); \ +} while(0) + if((sel = ovrtc->GetSelection()) > 0) + appendval("rtcEnabled"); + if((sel = ovst->GetSelection()) > 0) + appendval("saveType"); + if((sel = ovfs->GetSelection()) > 0) { + vba_over.append(wxT("flashSize=")); + vba_over.append(sel == 1 ? wxT("65536") : wxT("131072")); + vba_over.append(wxTextFile::GetEOL()); + cfg->Write(wxT("flashSize"), 0x10000 << (sel - 1)); + } + if((sel = ovmir->GetSelection()) > 0) + appendval("mirroringEnabled"); + cfg->SetPath(wxT("/")); + vba_over.append(wxTextFile::GetEOL()); + fn.Mkdir(0777, wxPATH_MKDIR_FULL); + wxTempFileOutputStream fos(fn.GetFullPath()); + fos.Write(vba_over.mb_str(), vba_over.size()); + fos.Commit(); + } + } +} + +EVT_HANDLER_MASK(DisplayConfigure, "Display options...", CMDEN_NREC_ANY) +{ + bool fs = gopts.fullscreen; + int max_scale = gopts.max_scale; + int renderer = gopts.render_method; + bool bilinear = gopts.bilinear; + int filt = gopts.filter; + wxString fp = gopts.filter_plugin; + wxVideoMode dm = gopts.fs_mode; + bool vsync = gopts.vsync; + + if(gopts.max_threads == 1) + gopts.max_threads = 0; + else { + gopts.max_threads = wxThread::GetCPUCount(); + if(gopts.max_threads > 8) + gopts.max_threads = 8; + } + wxDialog *dlg = GetXRCDialog("DisplayConfig"); + if(ShowModal(dlg) != wxID_OK) + return; + if(!gopts.max_threads) + gopts.max_threads = 1; + update_opts(); + if(max_scale != gopts.max_scale && panel->panel) { + if(gopts.max_scale) { + panel->panel->GetWindow()->SetMaxSize(wxDefaultSize); + if(fs == gopts.fullscreen && (!panel->IsFullScreen() || + dm == gopts.fs_mode)) + panel->Layout(); + // else the window will be resized anyway + else if(fs == gopts.fullscreen && (!panel->IsFullScreen() || + dm == gopts.fs_mode)) { + // can't compute max size here, so just destroy & rebuild + // drawing panel + panel->panel->Delete(); + panel->panel = NULL; + } + } + } + if(fs != gopts.fullscreen) + panel->ShowFullScreen(gopts.fullscreen); + else if(panel->IsFullScreen() && dm != gopts.fs_mode) { + // maybe not the best way to do this.. + panel->ShowFullScreen(false); + panel->ShowFullScreen(true); + } + if(panel->panel && + // change in renderer obviously requires restart + (renderer != gopts.render_method || + // change in bilinear filter requires restart only for 3d renderers + // but restart for all anyway + bilinear != gopts.bilinear || + // change in scale requires buffer resizing + builtin_ff_scale(filt) != builtin_ff_scale(gopts.filter) || + // plugin is only loaded on init + (filt == FF_PLUGIN && fp != gopts.filter_plugin) || + // ifb doesn't support 24-bit + (gopts.ifb != IFB_NONE && systemColorDepth == 24) || + // standard prefers 24-bit + (gopts.ifb == IFB_NONE && gopts.filter == FF_NONE && + systemColorDepth == 32 && gopts.render_method == 0) || + // vsync is only set in init + vsync != gopts.vsync)) { + panel->panel->Delete(); + panel->panel = NULL; + } +} + +EVT_HANDLER_MASK(ChangeFilter, "Change Pixel Filter", CMDEN_NREC_ANY) +{ + int filt = gopts.filter; + if(filt == FF_PLUGIN || + ++gopts.filter == FF_PLUGIN && gopts.filter_plugin.empty()) + gopts.filter = 0; + update_opts(); + if(panel->panel && + builtin_ff_scale(filt) != builtin_ff_scale(gopts.filter)) { + panel->panel->Delete(); + panel->panel = NULL; + } +} + +EVT_HANDLER_MASK(ChangeIFB, "Change Interframe Blending", CMDEN_NREC_ANY) +{ + gopts.ifb = (gopts.ifb + 1) % 3; + update_opts(); + if(panel->panel && + // ifb doesn't support 24-bit + (gopts.ifb != IFB_NONE && systemColorDepth == 24) || + // standard prefers 24-bit + (gopts.ifb == IFB_NONE && gopts.filter == FF_NONE && + systemColorDepth == 32 && gopts.render_method == 0)) { + panel->panel->Delete(); + panel->panel = NULL; + } +} + +EVT_HANDLER_MASK(SoundConfigure, "Sound options...", CMDEN_NREC_ANY) +{ + int oqual = gopts.sound_qual, oapi = gopts.audio_api; + bool oupmix = gopts.upmix, ohw = gopts.dsound_hw_accel; + wxString odev = gopts.audio_dev; + wxDialog *dlg = GetXRCDialog("SoundConfig"); + if(ShowModal(dlg) != wxID_OK) + return; + update_opts(); + switch(panel->game_type()) { + case IMAGE_UNKNOWN: + return; + case IMAGE_GB: + gb_effects_config.echo = (float)gopts.gb_echo / 100.0; + gb_effects_config.stereo = (float)gopts.gb_stereo / 100.0; + // note that setting declick may reset gb sound engine + gbSoundSetDeclicking(gopts.gb_declick); + gbSoundSetSampleRate(!gopts.sound_qual ? 48000 : + 44100 / (1 << (gopts.sound_qual - 1))); + break; + case IMAGE_GBA: + soundSetSampleRate(!gopts.sound_qual ? 48000 : + 44100 / (1 << (gopts.sound_qual - 1))); + break; + } + // changing sample rate causes driver reload, so no explicit reload needed + if(oqual == gopts.sound_qual && + // otherwise reload if API changes + (oapi != gopts.audio_api || odev != gopts.audio_dev || + // or init-only options + (oapi == AUD_XAUDIO2 && oupmix != gopts.upmix) || + (oapi == AUD_DIRECTSOUND && ohw != gopts.dsound_hw_accel))) { + soundShutdown(); + soundInit(); + } + soundSetVolume((float)gopts.sound_vol / 100.0); +} + +EVT_HANDLER(EmulatorDirectories, "Directories...") +{ + wxDialog *dlg = GetXRCDialog("DirectoriesConfig"); + if(ShowModal(dlg) == wxID_OK) + update_opts(); +} + +EVT_HANDLER(JoypadConfigure, "Joypad options...") +{ + wxDialog *dlg = GetXRCDialog("JoypadConfig"); + joy.Attach(NULL); + joy.Add(); + if(ShowModal(dlg) == wxID_OK) + update_opts(); + SetJoystick(); +} + +// new +EVT_HANDLER(LinkConfigure, "Link options...") +{ +#ifndef NO_LINK + bool jb = gba_joybus_enabled; + wxString jh = gopts.joybus_host; + wxDialog *dlg = GetXRCDialog("LinkConfig"); + if(ShowModal(dlg) != wxID_OK) + return; + update_opts(); + if(jb != gba_joybus_enabled) { + if(gba_joybus_enabled) + JoyBusConnect(); + else + JoyBusShutdown(); + } else if(jh != gopts.joybus_host) { + joybusHostAddr = std::string(gopts.joybus_host.mb_str()); + JoyBusConnect(); + } + if(gba_link_enabled != did_link_init) { + if(gba_link_enabled) { + if((did_link_init = InitLink())) + cmd_enable |= CMDEN_LINK_ANY; + } else { + did_link_init = false; + CloseLink(); + lanlink.active = false; + cmd_enable &= ~CMDEN_LINK_ANY; + } + enable_menus(); + } +#endif +} + +EVT_HANDLER(Customize, "Customize UI...") +{ + wxDialog *dlg = GetXRCDialog("AccelConfig"); + if(ShowModal(dlg) == wxID_OK) + update_opts(); +} + +EVT_HANDLER(BugReport, "Report bugs...") +{ + wxLaunchDefaultBrowser(wxT("http://sourceforge.net/tracker/?group_id=212795&atid=1023154")); +} + +EVT_HANDLER(FAQ, "VBA-M support forum") +{ + wxLaunchDefaultBrowser(wxT("http://vba-m.com/forum/")); +} + +// was About +EVT_HANDLER(wxID_ABOUT, "About...") +{ + wxAboutDialogInfo ai; + ai.SetName(wxT("VisualBoyAdvance-M")); + ai.SetVersion(wxT(VERSION)); + // setting website, icon, license uses custom aboutbox on win32 & macosx + // but at least win32 standard about is nothing special + ai.SetWebSite(wxT("http://www.vba-m.com/")); + ai.SetIcon(GetIcon()); + ai.SetDescription(_("Nintendo GameBoy (+Color+Advance) emulator.")); + ai.SetCopyright(_("Copyright (C) 1999-2003 Forgotten\nCopyright (C) 2004-2006 VBA development team\nCopyright (C) 2007-2011 VBA-M development team")); + ai.SetLicense(_("This program is free software: you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation, either version 2 of the License, or\n" + "(at your option) any later version.\n\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program. If not, see http://www.gnu.org/licenses .")); + // from gtk + ai.AddDeveloper(wxT("Forgotten")); + ai.AddDeveloper(wxT("kxu")); + ai.AddDeveloper(wxT("Pokemonhacker")); + ai.AddDeveloper(wxT("Spacy51")); + ai.AddDeveloper(wxT("mudlord")); + ai.AddDeveloper(wxT("Nach")); + ai.AddDeveloper(wxT("jbo_85")); + ai.AddDeveloper(wxT("bgK")); + ai.AddArtist(wxT("Matteo Drera")); + ai.AddArtist(wxT("Jakub Steiner")); + ai.AddArtist(wxT("Jones Lee")); + // from win32 + ai.AddDeveloper(wxT("Jonas Quinn")); + ai.AddDeveloper(wxT("DJRobX")); + ai.AddDeveloper(wxT("Spacy")); + ai.AddDeveloper(wxT("Squall Leonhart")); + // wx + ai.AddDeveloper(wxT("Thomas J. Moore")); + // from win32 "thanks" + ai.AddDeveloper(wxT("blargg")); + ai.AddDeveloper(wxT("Costis")); + ai.AddDeveloper(wxT("chrono")); + ai.AddDeveloper(wxT("xKiv")); + ai.AddDeveloper(wxT("Orig. VBA team")); + + wxAboutBox(ai); +} + +// Dummy for disabling system key bindings +EVT_HANDLER_MASK(NOOP, "Do nothing", CMDEN_NEVER) +{ +} + +// The following have been moved to dialogs +// I will not implement as command unless there is great demand +// CheatsList +//EVT_HANDLER(CheatsLoad, "Load Cheats...") +//EVT_HANDLER(CheatsSave, "Save Cheats...") +//GeneralConfigure +//EVT_HANDLER(EmulatorRewindInterval, "EmulatorRewindInterval") +//EVT_HANDLER(EmulatorAutoApplyPatchFiles, "EmulatorAutoApplyPatchFiles") +//EVT_HANDLER(ThrottleNone, "ThrottleNone") +//EVT_HANDLER(Throttle025%, "Throttle025%") +//EVT_HANDLER(Throttle050%, "Throttle050%") +//EVT_HANDLER(Throttle100%, "Throttle100%") +//EVT_HANDLER(Throttle150%, "Throttle150%") +//EVT_HANDLER(Throttle200%, "Throttle200%") +//EVT_HANDLER(ThrottleOther, "ThrottleOther") +//GameBoyConfigure/GameBoyAdvanceConfigure +//EVT_HANDLER(FrameSkip0, "FrameSkip0") +//EVT_HANDLER(FrameSkip1, "FrameSkip1") +//EVT_HANDLER(FrameSkip2, "FrameSkip2") +//EVT_HANDLER(FrameSkip3, "FrameSkip3") +//EVT_HANDLER(FrameSkip4, "FrameSkip4") +//EVT_HANDLER(FrameSkip5, "FrameSkip5") +//EVT_HANDLER(FrameSkip6, "FrameSkip6") +//EVT_HANDLER(FrameSkip7, "FrameSkip7") +//EVT_HANDLER(FrameSkip8, "FrameSkip8") +//EVT_HANDLER(FrameSkip9, "FrameSkip9") +// GameBoyConfigure +//EVT_HANDLER(GameboyBorder, "GameboyBorder") +//EVT_HANDLER(GameboyBorderAutomatic, "GameboyBorderAutomatic") +//EVT_HANDLER(GameboyColors, "GameboyColors") +//GameBoyAdvanceConfigure +//EVT_HANDLER(EmulatorAGBPrint, "EmulatorAGBPrint") +//EVT_HANDLER(EmulatorSaveAuto, "EmulatorSaveAuto") +//EVT_HANDLER(EmulatorSaveEEPROM, "EmulatorSaveEEPROM") +//EVT_HANDLER(EmulatorSaveSRAM, "EmulatorSaveSRAM") +//EVT_HANDLER(EmulatorSaveFLASH, "EmulatorSaveFLASH") +//EVT_HANDLER(EmulatorSaveEEPROMSensor, "EmulatorSaveEEPROMSensor") +//EVT_HANDLER(EmulatorSaveFlash64K, "EmulatorSaveFlash64K") +//EVT_HANDLER(EmulatorSaveFlash128K, "EmulatorSaveFlash128K") +//EVT_HANDLER(EmulatorSaveDetectNow, "EmulatorSaveDetectNow") +//EVT_HANDLER(EmulatorRTC, "EmulatorRTC") +//DisplayConfigure +//EVT_HANDLER(EmulatorShowSpeedNone, "EmulatorShowSpeedNone") +//EVT_HANDLER(EmulatorShowSpeedPercentage, "EmulatorShowSpeedPercentage") +//EVT_HANDLER(EmulatorShowSpeedDetailed, "EmulatorShowSpeedDetailed") +//EVT_HANDLER(EmulatorShowSpeedTransparent, "EmulatorShowSpeedTransparent") +//EVT_HANDLER(VideoX1, "VideoX1") +//EVT_HANDLER(VideoX2, "VideoX2") +//EVT_HANDLER(VideoX3, "VideoX3") +//EVT_HANDLER(VideoX4, "VideoX4") +//EVT_HANDLER(VideoX5, "VideoX5") +//EVT_HANDLER(VideoX6, "VideoX6") +//EVT_HANDLER(Video320x240, "Video320x240") +//EVT_HANDLER(Video640x480, "Video640x480") +//EVT_HANDLER(Video800x600, "Video800x600") +//EVT_HANDLER(VideoFullscreen, "VideoFullscreen") +//EVT_HANDLER(VideoFullscreenMaxScale, "VideoFullscreenMaxScale") +//EVT_HANDLER(VideoRenderDDRAW, "VideoRenderDDRAW") +//EVT_HANDLER(VideoRenderD3D, "VideoRenderD3D") +//EVT_HANDLER(VideoRenderOGL, "VideoRenderOGL") +//EVT_HANDLER(VideoVsync, "VideoVsync") +//EVT_HANDLER(FilterNormal, "FilterNormal") +//EVT_HANDLER(FilterTVMode, "FilterTVMode") +//EVT_HANDLER(Filter2xSaI, "Filter2xSaI") +//EVT_HANDLER(FilterSuper2xSaI, "FilterSuper2xSaI") +//EVT_HANDLER(FilterSuperEagle, "FilterSuperEagle") +//EVT_HANDLER(FilterPixelate, "FilterPixelate") +//EVT_HANDLER(FilterMotionBlur, "FilterMotionBlur") +//EVT_HANDLER(FilterAdMameScale2x, "FilterAdMameScale2x") +//EVT_HANDLER(FilterSimple2x, "FilterSimple2x") +//EVT_HANDLER(FilterBilinear, "FilterBilinear") +//EVT_HANDLER(FilterBilinearPlus, "FilterBilinearPlus") +//EVT_HANDLER(FilterScanlines, "FilterScanlines") +//EVT_HANDLER(FilterHq2x, "FilterHq2x") +//EVT_HANDLER(FilterLq2x, "FilterLq2x") +//EVT_HANDLER(FilterIFBNone, "FilterIFBNone") +//EVT_HANDLER(FilterIFBMotionBlur, "FilterIFBMotionBlur") +//EVT_HANDLER(FilterIFBSmart, "FilterIFBSmart") +//EVT_HANDLER(FilterDisableMMX, "FilterDisableMMX") +//JoypadConfigure +//EVT_HANDLER(JoypadConfigure1, "JoypadConfigure1") +//EVT_HANDLER(JoypadConfigure2, "JoypadConfigure2") +//EVT_HANDLER(JoypadConfigure3, "JoypadConfigure3") +//EVT_HANDLER(JoypadConfigure4, "JoypadConfigure4") +//EVT_HANDLER(JoypadMotionConfigure, "JoypadMotionConfigure") + +// The following functionality has been removed +// It should be done in OS, rather than in vbam +//EVT_HANDLER(EmulatorAssociate, "EmulatorAssociate") + +// The following functionality has been removed +// It should be done at OS level (e.g. window manager) +//EVT_HANDLER(SystemMinimize, "SystemMinimize") diff --git a/src/wx/copy-events.cmake b/src/wx/copy-events.cmake new file mode 100644 index 00000000..06b30de7 --- /dev/null +++ b/src/wx/copy-events.cmake @@ -0,0 +1,56 @@ +# Create cmdtab.cpp, cmdhandlers.h, cmd-evtable.h from cmdevents.cpp + +IF(NOT OUTDIR) + SET(OUTDIR ".") +ENDIF(NOT OUTDIR) + +SET(CMDTAB "${OUTDIR}/cmdtab.cpp") +SET(EVPROTO "${OUTDIR}/cmdhandlers.h") +SET(EVTABLE "${OUTDIR}/cmd-evtable.h") + +FILE(READ cmdevents.cpp MW) +STRING(REGEX MATCHALL "\nEVT_HANDLER([^\")]|\"[^\"]*\")*\\)" MW "${MW}") + +# cmdtab.cpp is a table of cmd-id-name/cmd-name pairs +# sorted for binary searching +FILE(WRITE "${CMDTAB}" "// Generated from cmdevents.cpp; do not edit\n#include \"wxvbam.h\"\n\nstruct cmditem cmdtab[] = {\n") +SET(EVLINES ) +FOREACH(EV ${MW}) + # stripping the wxID_ makes it look better, but it's still all-caps + STRING(REGEX REPLACE "^[^\"]*\\((wxID_|)([^,]*),[^\"]*(\"[^\"]*\")[^,)]*(,[^)]*|).*" + " {wxT(\"\\2\"), wxTRANSLATE(\\3), XRCID(\"\\1\\2\")\\4 }" + EV "${EV}") + STRING(REGEX REPLACE "XRCID\\(\"(wxID_[^\"]*)\"\\)" "\\1" EV ${EV}) + LIST(APPEND EVLINES "${EV},\n") +ENDFOREACH(EV) +LIST(SORT EVLINES) +STRING(REGEX REPLACE ",\n\$" "\n" EVLINES "${EVLINES}") +FILE(APPEND "${CMDTAB}" ${EVLINES}) +FILE(APPEND "${CMDTAB}" "};\nconst int ncmds = sizeof(cmdtab) / sizeof(cmdtab[0]);\n") + +# cmdhandlers.h contains prototypes for all handlers +FILE(WRITE "${EVPROTO}" "// Generated from cmdevents.cpp; do not edit\n") +FOREACH(EV ${MW}) + STRING(REGEX REPLACE "^[^\"]*\\(" "void On" P1 "${EV}") + STRING(REGEX REPLACE ",.*" "(wxCommandEvent&);\n" P1 "${P1}") + FILE(APPEND "${EVPROTO}" "${P1}") + IF(EV MATCHES "EVT_HANDLER_MASK") + STRING(REGEX REPLACE "^[^\"]*\\(" "void Do" P1 "${EV}") + STRING(REGEX REPLACE ",.*" "();\n" P1 "${P1}") + FILE(APPEND "${EVPROTO}" "${P1}") + ENDIF(EV MATCHES "EVT_HANDLER_MASK") +ENDFOREACH(EV) + +# cmd-evtable.h has the event table entries for all handlers +FILE(WRITE "${EVTABLE}" "// Generated from cmdevents.cpp; do not edit\n") +FOREACH(EV ${MW}) + FILE(APPEND "${EVTABLE}" "EVT_MENU(") + STRING(REGEX REPLACE "[^\"]*\\(" "" EV "${EV}") + STRING(REGEX REPLACE ",.*" "" EV "${EV}") + IF("${EV}" MATCHES "wx.*") + FILE(APPEND "${EVTABLE}" "${EV}") + ELSE("${EV}" MATCHES "wx.*") + FILE(APPEND "${EVTABLE}" "XRCID(\"${EV}\")") + ENDIF("${EV}" MATCHES "wx.*") + FILE(APPEND "${EVTABLE}" ", MainFrame::On${EV})\n") +ENDFOREACH(EV) diff --git a/src/wx/drawing.h b/src/wx/drawing.h new file mode 100644 index 00000000..e65264f8 --- /dev/null +++ b/src/wx/drawing.h @@ -0,0 +1,87 @@ +#ifndef GAME_DRAWING_H +#define GAME_DRAWING_H + +class BasicDrawingPanel : public DrawingPanel, public wxPanel +{ +public: + BasicDrawingPanel(wxWindow *parent, int _width, int _height); + wxWindow *GetWindow() { return this; } + void Delete() { Destroy(); } + +protected: + void PaintEv2(wxPaintEvent &ev) { PaintEv(ev); } + void DrawArea(wxWindowDC &dc); + + DECLARE_CLASS() + DECLARE_EVENT_TABLE() +}; + +#ifndef NO_OGL +#include + +class GLDrawingPanel : public DrawingPanel, public wxGLCanvas +{ +public: + GLDrawingPanel(wxWindow *parent, int _width, int _height); + virtual ~GLDrawingPanel(); + wxWindow *GetWindow() { return this; } + void Delete() { Destroy(); } + +protected: + void PaintEv2(wxPaintEvent &ev) { PaintEv(ev); } + void OnSize(wxSizeEvent &); + void DrawArea(wxWindowDC &dc); +#if wxCHECK_VERSION(2,9,0) || !defined(__WXMAC__) + wxGLContext ctx; +#endif + bool did_init; + void Init(); + GLuint texid, vlist; + int texsize; + + DECLARE_CLASS() + DECLARE_EVENT_TABLE() +}; +#endif + +#if defined(__WXMSW__) && !defined(NO_D3D) +class DXDrawingPanel : public DrawingPanel, public wxPanel +{ +public: + DXDrawingPanel(wxWindow *parent, int _width, int _height); + wxWindow *GetWindow() { return this; } + void Delete() { Destroy(); } + +protected: + void PaintEv2(wxPaintEvent &ev) { PaintEv(ev); } + void DrawArea(wxWindowDC&); + bool did_init; + void Init(); + + DECLARE_CLASS() + DECLARE_EVENT_TABLE() +}; +#endif + +#ifndef NO_CAIRO +#include + +class CairoDrawingPanel : public DrawingPanel, public wxPanel +{ +public: + CairoDrawingPanel(wxWindow *parent, int _width, int _height); + ~CairoDrawingPanel(); + wxWindow *GetWindow() { return this; } + void Delete() { Destroy(); } + +protected: + void PaintEv2(wxPaintEvent &ev) { PaintEv(ev); } + void DrawArea(wxWindowDC&); + cairo_surface_t *conv_surf; + + DECLARE_CLASS() + DECLARE_EVENT_TABLE() +}; +#endif + +#endif /* GAME_DRAWING_H */ diff --git a/src/wx/dsound.cpp b/src/wx/dsound.cpp new file mode 100644 index 00000000..abf275c3 --- /dev/null +++ b/src/wx/dsound.cpp @@ -0,0 +1,334 @@ +// Application +#include "wxvbam.h" + +// Internals +#include "../System.h" +#include "../gba/GBA.h" +#include "../gba/Globals.h" +#include "../gba/Sound.h" +#include "../common/SoundDriver.h" + +// DirectSound8 +#define DIRECTSOUND_VERSION 0x0800 +#include + +extern bool soundBufferLow; + +class DirectSound : public SoundDriver +{ +private: + LPDIRECTSOUND8 pDirectSound; // DirectSound interface + LPDIRECTSOUNDBUFFER dsbPrimary; // Primary DirectSound buffer + LPDIRECTSOUNDBUFFER dsbSecondary; // Secondary DirectSound buffer + LPDIRECTSOUNDNOTIFY8 dsbNotify; + HANDLE dsbEvent; + WAVEFORMATEX wfx; // Primary buffer wave format + int soundBufferLen; + int soundBufferTotalLen; + unsigned int soundNextPosition; + +public: + DirectSound(); + virtual ~DirectSound(); + + bool init(long sampleRate); // initialize the primary and secondary sound buffer + void pause(); // pause the secondary sound buffer + void reset(); // stop and reset the secondary sound buffer + void resume(); // resume the secondary sound buffer + void write(u16 * finalWave, int length); // write the emulated sound to the secondary sound buffer +}; + + +DirectSound::DirectSound() +{ + pDirectSound = NULL; + dsbPrimary = NULL; + dsbSecondary = NULL; + dsbNotify = NULL; + dsbEvent = NULL; + + soundBufferTotalLen = 14700; + soundNextPosition = 0; +} + + +DirectSound::~DirectSound() +{ + if(dsbNotify) { + dsbNotify->Release(); + dsbNotify = NULL; + } + + if(dsbEvent) { + CloseHandle(dsbEvent); + dsbEvent = NULL; + } + + if(pDirectSound) { + if(dsbPrimary) { + dsbPrimary->Release(); + dsbPrimary = NULL; + } + + if(dsbSecondary) { + dsbSecondary->Release(); + dsbSecondary = NULL; + } + + pDirectSound->Release(); + pDirectSound = NULL; + } +} + + +bool DirectSound::init(long sampleRate) +{ + HRESULT hr; + DWORD freq; + DSBUFFERDESC dsbdesc; + int i; + + hr = CoCreateInstance( CLSID_DirectSound8, NULL, CLSCTX_INPROC_SERVER, IID_IDirectSound8, (LPVOID *)&pDirectSound ); + if( hr != S_OK ) { + wxLogError(_("Cannot create DirectSound %08x"), hr); + return false; + } + + GUID dev; + if(gopts.audio_dev.empty()) + dev = DSDEVID_DefaultPlayback; + else + CLSIDFromString(const_cast(gopts.audio_dev.c_str()), &dev); + pDirectSound->Initialize( &dev ); + if( hr != DS_OK ) { + wxLogError(_("Cannot create DirectSound %08x"), hr); + return false; + } + + if( FAILED( hr = pDirectSound->SetCooperativeLevel( (HWND)wxGetApp().frame->GetHandle(), DSSCL_EXCLUSIVE ) ) ) { + wxLogError(_("Cannot SetCooperativeLevel %08x"), hr); + return false; + } + + + // Create primary sound buffer + ZeroMemory( &dsbdesc, sizeof(DSBUFFERDESC) ); + dsbdesc.dwSize = sizeof(DSBUFFERDESC); + dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER; + if( !gopts.dsound_hw_accel ) { + dsbdesc.dwFlags |= DSBCAPS_LOCSOFTWARE; + } + + if( FAILED( hr = pDirectSound->CreateSoundBuffer( &dsbdesc, &dsbPrimary, NULL ) ) ) { + wxLogError(_("Cannot CreateSoundBuffer %08x"), hr); + return false; + } + + freq = sampleRate; + // calculate the number of samples per frame first + // then multiply it with the size of a sample frame (16 bit * stereo) + soundBufferLen = ( freq / 60 ) * 4; + soundBufferTotalLen = soundBufferLen * 10; + soundNextPosition = 0; + + ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = 2; + wfx.nSamplesPerSec = freq; + wfx.wBitsPerSample = 16; + wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + + if( FAILED( hr = dsbPrimary->SetFormat( &wfx ) ) ) { + wxLogError( _("CreateSoundBuffer(primary) failed %08x"), hr ); + return false; + } + + + // Create secondary sound buffer + ZeroMemory( &dsbdesc, sizeof(DSBUFFERDESC) ); + dsbdesc.dwSize = sizeof(DSBUFFERDESC); + dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS; + if( !gopts.dsound_hw_accel ) { + dsbdesc.dwFlags |= DSBCAPS_LOCSOFTWARE; + } + dsbdesc.dwBufferBytes = soundBufferTotalLen; + dsbdesc.lpwfxFormat = &wfx; + + if( FAILED( hr = pDirectSound->CreateSoundBuffer( &dsbdesc, &dsbSecondary, NULL ) ) ) { + wxLogError(_("CreateSoundBuffer(secondary) failed %08x"), hr ); + return false; + } + + if( FAILED( hr = dsbSecondary->SetCurrentPosition( 0 ) ) ) { + wxLogError(_("dsbSecondary->SetCurrentPosition failed %08x"), hr ); + return false; + } + + + if( SUCCEEDED( hr = dsbSecondary->QueryInterface( IID_IDirectSoundNotify8, (LPVOID*)&dsbNotify ) ) ) { + dsbEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + DSBPOSITIONNOTIFY notify[10]; + for( i = 0; i < 10; i++ ) { + notify[i].dwOffset = i * soundBufferLen; + notify[i].hEventNotify = dsbEvent; + } + + if( FAILED( dsbNotify->SetNotificationPositions( 10, notify ) ) ) { + dsbNotify->Release(); + dsbNotify = NULL; + CloseHandle(dsbEvent); + dsbEvent = NULL; + } + } + + + // Play primary buffer + if( FAILED( hr = dsbPrimary->Play( 0, 0, DSBPLAY_LOOPING ) ) ) { + wxLogError( _("Cannot Play primary %08x"), hr ); + return false; + } + + return true; +} + + +void DirectSound::pause() +{ + if( dsbSecondary == NULL ) return; + + DWORD status; + + dsbSecondary->GetStatus( &status ); + + if( status & DSBSTATUS_PLAYING ) dsbSecondary->Stop(); +} + + +void DirectSound::reset() +{ + if( dsbSecondary == NULL ) return; + + dsbSecondary->Stop(); + + dsbSecondary->SetCurrentPosition( 0 ); + + soundNextPosition = 0; +} + + +void DirectSound::resume() +{ + if( dsbSecondary == NULL ) return; + + dsbSecondary->Play( 0, 0, DSBPLAY_LOOPING ); +} + + +void DirectSound::write(u16 * finalWave, int length) +{ + if(!pDirectSound) return; + + + HRESULT hr; + DWORD status = 0; + DWORD play = 0; + LPVOID lpvPtr1; + DWORD dwBytes1 = 0; + LPVOID lpvPtr2; + DWORD dwBytes2 = 0; + + if( !speedup && synchronize && !gopts.throttle ) { + hr = dsbSecondary->GetStatus(&status); + if( status & DSBSTATUS_PLAYING ) { + if( !soundPaused ) { + while( true ) { + dsbSecondary->GetCurrentPosition(&play, NULL); + int BufferLeft = ((soundNextPosition <= play) ? + play - soundNextPosition : + soundBufferTotalLen - soundNextPosition + play); + + if(BufferLeft > soundBufferLen) + { + if (BufferLeft > soundBufferTotalLen - (soundBufferLen * 3)) + soundBufferLow = true; + break; + } + soundBufferLow = false; + + if(dsbEvent) { + WaitForSingleObject(dsbEvent, 50); + } + } + } + }/* else { + // TODO: remove? + setsoundPaused(true); + }*/ + } + + + // Obtain memory address of write block. + // This will be in two parts if the block wraps around. + if( DSERR_BUFFERLOST == ( hr = dsbSecondary->Lock( + soundNextPosition, + soundBufferLen, + &lpvPtr1, + &dwBytes1, + &lpvPtr2, + &dwBytes2, + 0 ) ) ) { + // If DSERR_BUFFERLOST is returned, restore and retry lock. + dsbSecondary->Restore(); + hr = dsbSecondary->Lock( + soundNextPosition, + soundBufferLen, + &lpvPtr1, + &dwBytes1, + &lpvPtr2, + &dwBytes2, + 0 ); + } + + soundNextPosition += soundBufferLen; + soundNextPosition = soundNextPosition % soundBufferTotalLen; + + if( SUCCEEDED( hr ) ) { + // Write to pointers. + CopyMemory( lpvPtr1, finalWave, dwBytes1 ); + if ( lpvPtr2 ) { + CopyMemory( lpvPtr2, finalWave + dwBytes1, dwBytes2 ); + } + + // Release the data back to DirectSound. + hr = dsbSecondary->Unlock( lpvPtr1, dwBytes1, lpvPtr2, dwBytes2 ); + } else { + wxLogError(_("dsbSecondary->Lock() failed: %08x"), hr ); + return; + } +} + +SoundDriver *newDirectSound() +{ + return new DirectSound(); +} + +struct devnames { + wxArrayString *names, *ids; +}; + +static BOOL CALLBACK DSEnumCB(LPGUID guid, LPCTSTR desc, LPCTSTR drvnam, LPVOID user) +{ + devnames *dn = (devnames *)user; + dn->names->push_back(desc); + WCHAR buf[32 + 4 + 2 + 1]; // hex digits + "-" + "{}" + \0 + StringFromGUID2(*guid, buf, sizeof(buf)); + dn->ids->push_back(buf); + return TRUE; +} + +bool GetDSDevices(wxArrayString &names, wxArrayString &ids) +{ + devnames dn = { &names, &ids }; + return DirectSoundEnumerate(DSEnumCB, (LPVOID)&dn) == DS_OK; +} diff --git a/src/wx/filters.h b/src/wx/filters.h new file mode 100644 index 00000000..fe8bd5d8 --- /dev/null +++ b/src/wx/filters.h @@ -0,0 +1,77 @@ +#ifndef FILTERS_H +#define FILTERS_H + +// FIXME: these should be in a system header included by all users and all +// files which define these functions +// most 16-bit filters require space in src rounded up to u32 +// those that take delta take 1 src line of pixels, rounded up to u32 size +// initial value appears to be all-0xff +void Pixelate32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Pixelate(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +// next 3*2 use Init_2xSaI(555|565) and do not take into account +int Init_2xSaI(u32 BitFormat); +// endianness or bit shift variables in init. +// next 4*2 may be MMX-accelerated +void _2xSaI32(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +void _2xSaI(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +// void Scale_2xSaI(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Super2xSaI32(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +void Super2xSaI(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +void SuperEagle32(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +void SuperEagle(u8 *src, u32 spitch, u8 *delta, u8 *dst, u32 dstp, int w, int h); +void AdMame2x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void AdMame2x(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// next 4 convert to rgb24 in internal buffers first, and then back again +void Bilinear32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Bilinear(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void BilinearPlus32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void BilinearPlus(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Scanlines32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Scanlines(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void ScanlinesTV32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// "TV" here means each pixel is faded horizontally & vertically rather than +// inserting black scanlines +// this depends on RGB_LOW_BITS_MASK +extern int RGB_LOW_BITS_MASK; +void ScanlinesTV(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// next 2 require calling hq2x_init first and whenever bpp changes +void hq2x_init(unsigned bpp); +void hq2x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void hq2x(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void lq2x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void lq2x(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// the simple ones could greatly benefit from correct usage of preprocessor.. +// in any case, they are worthless, since all renderers do "simple" or +// better by default +void Simple2x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Simple2x(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Simple3x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Simple3x(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Simple4x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void Simple4x(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// note: 16-bit input for asm version only! +void hq3x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// this takes 32-bit input +// (by converting to 16-bit first in asm version) +void hq3x32_32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void hq3x16(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// note: 16-bit input for asm version only! +void hq4x32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +// this takes 32-bit input +// (by converting to 16-bit first in asm version) +void hq4x32_32(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); +void hq4x16(u8 *src, u32 spitch, u8 *, u8 *dst, u32 dstp, int w, int h); + +// call ifc to ignore previous frame / when starting new +void InterframeCleanup(); +// all 4 are MMX-accelerated if enabled +void SmartIB(u8 *src, u32 spitch, int width, int height); +void SmartIB32(u8 *src, u32 spitch, int width, int height); +void MotionBlurIB(u8 *src, u32 spitch, int width, int height); +void MotionBlurIB32(u8 *src, u32 spitch, int width, int height); +void SmartIB(u8 *src, u32 spitch, int width, int starty, int height); +void SmartIB32(u8 *src, u32 spitch, int width, int starty, int height); +void MotionBlurIB(u8 *src, u32 spitch, int width, int starty, int height); +void MotionBlurIB32(u8 *src, u32 spitch, int width, int starty, int height); + +#endif /* FILTERS_H */ diff --git a/src/wx/gfxviewers.cpp b/src/wx/gfxviewers.cpp new file mode 100644 index 00000000..34ce55ab --- /dev/null +++ b/src/wx/gfxviewers.cpp @@ -0,0 +1,1565 @@ + +// these are all the viewer dialogs with graphical panel areas +// they can be instantiated multiple times + +#include "wxvbam.h" +#include "viewsupt.h" +#include +#include + +// FIXME: many of these read e.g. palette data directly without regard to +// byte order. Need to determine where things are stored in emulated machine +// order and where in native order, and swap the latter on big-endian + +// most of these have label fields that need to be sized and later filled +// mv is a string to initialize with for sizing +#define getlab(v, n, mv) do { \ + v = XRCCTRL(*this, n, wxControl); \ + if(!v) \ + baddialog(); \ + v->SetLabel(wxT(mv)); \ +} while(0) + +// FIXME: this should be in a header +extern u8 gbInvertTab[256]; + +// avoid exporting classes +namespace Viewers { + class MapViewer : public GfxViewer + { + public: + MapViewer() : GfxViewer(wxT("MapViewer"), 1024, 1024) + { + frame = bg = 0; + getradio(fr0 = , "Frame0", frame, 0); + getradio(fr1 = , "Frame1", frame, 0xa000); + getradio(bg0 = , "BG0", bg, 0); + getradio(bg1 = , "BG1", bg, 1); + getradio(bg2 = , "BG2", bg, 2); + getradio(bg3 = , "BG3", bg, 3); + getlab(modelab, "Mode", "8"); + getlab(mapbase, "MapBase", "0xWWWWWWWW"); + getlab(charbase, "CharBase", "0xWWWWWWWW"); + getlab(size, "Size", "1024x1024"); + getlab(colors, "Colors", "2WW"); + getlab(prio, "Priority", "3"); + getlab(mosaic, "Mosaic", "0"); + getlab(overflow, "Overflow", "0"); + getlab(coords, "Coords", "(1023,1023)"); + getlab(addr, "Address", "0xWWWWWWWW"); + getlab(tile, "Tile", "1023"); + getlab(flip, "Flip", "HV"); + getlab(palette, "Palette", "---"); + Fit(); + selx = sely = -1; + Update(); + } + void Update() + { + mode = DISPCNT & 7; + switch(bg) { + case 0: + control = BG0CNT; + break; + case 1: + control = BG1CNT; + break; + case 2: + control = BG2CNT; + break; + case 3: + control = BG3CNT; + break; + } + bool fr0en = true, fr1en = true, bg0en = true, bg1en = true, + bg2en = true, bg3en = true; + switch(mode) { + case 0: + fr0en = fr1en = false; + renderTextScreen(); + break; + case 1: + fr0en = fr1en = false; + bg3en = false; + if(bg == 3) { + bg = 0; + control = BG0CNT; + bg0->SetValue(true); + } + if(bg < 2) + renderTextScreen(); + else + renderRotScreen(); + break; + case 2: + fr0en = fr1en = false; + bg0en = bg1en = false; + if(bg < 2) { + bg = 2; + control = BG2CNT; + bg2->SetValue(true); + } + renderRotScreen(); + break; + case 3: + fr0en = fr1en = false; + bg0en = bg1en = bg2en = bg3en = false; + bg = 2; + bg2->SetValue(true); + renderMode3(); + break; + case 4: + bg0en = bg1en = bg2en = bg3en = false; + bg = 2; + bg2->SetValue(true); + renderMode4(); + break; + case 5: + case 6: + case 7: + bg = 2; + bg2->SetValue(true); + renderMode5(); + break; + } + ChangeBMP(); + fr0->Enable(fr0en); + fr1->Enable(fr1en); + bg0->Enable(bg0en); + bg1->Enable(bg1en); + bg2->Enable(bg2en); + bg3->Enable(bg3en); + + wxString s; + s.Printf(wxT("%d"), (int)mode); + modelab->SetLabel(s); + if(mode >= 3) { + mapbase->SetLabel(wxEmptyString); + charbase->SetLabel(wxEmptyString); + } else { + s.Printf(wxT("0x%08X"), ((control >> 8) & 0x1f) * 0x800 + 0x6000000); + mapbase->SetLabel(s); + s.Printf(wxT("0x%08X"), ((control >> 2) & 0x03) * 0x4000 + 0x6000000); + charbase->SetLabel(s); + } + s.Printf(wxT("%dx%d"), gv->bmw, gv->bmh); + size->SetLabel(s); + colors->SetLabel(control & 0x80 ? wxT("256") : wxT("16")); + s.Printf(wxT("%d"), control & 3); + prio->SetLabel(s); + mosaic->SetLabel(control & 0x40 ? wxT("1") : wxT("0")); + overflow->SetLabel(bg <= 1 ? wxEmptyString : + control & 0x2000 ? wxT("1") : wxT("0")); + UpdateMouseInfo(); + } + + void UpdateMouseInfoEv(wxMouseEvent &ev) + { + selx = ev.GetX(); + sely = ev.GetY(); + UpdateMouseInfo(); // note that this will be inaccurate if game + // not paused since last refresh + } + + u32 AddressFromSel() + { + u32 base = ((control >> 8) & 0x1f) * 0x800 + 0x6000000; + // all text bgs (16 bits) + if(mode == 0 || (mode < 3 && bg < 2) || mode == 6 || mode == 7) { + if(sely > 255) { + base += 0x800; + if(gv->bmw > 256) + base += 0x800; + } + if(selx >= 256) + base += 0x800; + return base + ((selx & 0xff) >> 3) * 2 + 64 * ((sely & 0xff) >> 3); + } + // rot bgs (8 bits) + if(mode < 3) + return base + (selx>>3) + (gv->bmw>>3) * (sely >> 3); + // mode 3/5 (16 bits) + if(mode != 4) + return 0x6000000 + 0xa000*frame + (selx + gv->bmw * sely) * 2; + // mode 4 (8 bits) + return 0x6000000 + 0xa000*frame + selx + gv->bmw * sely; + } + + void UpdateMouseInfo() + { + if(selx > gv->bmw || sely > gv->bmh) + selx = sely = -1; + if(selx < 0) { + coords->SetLabel(wxEmptyString); + addr->SetLabel(wxEmptyString); + tile->SetLabel(wxEmptyString); + flip->SetLabel(wxEmptyString); + palette->SetLabel(wxEmptyString); + } else { + wxString s; + s.Printf(wxT("(%d,%d)"), selx, sely); + coords->SetLabel(s); + u32 address = AddressFromSel(); + s.Printf(wxT("0x%08X"), address); + addr->SetLabel(s); + + if(!mode || (mode < 3 || mode > 5) && bg < 2) { + u16 value = *((u16 *)&vram[address - 0x6000000]); + s.Printf(wxT("%d"), value & 1023); + tile->SetLabel(s); + s = value & 1024 ? wxT('H') : wxT('-'); + s += value & 2048 ? wxT('V') : wxT('-'); + flip->SetLabel(s); + if(control & 0x80) + palette->SetLabel(wxT("---")); + else { + s.Printf(wxT("%d"), (value >> 12) & 15); + palette->SetLabel(s); + } + } else { + tile->SetLabel(wxT("---")); + flip->SetLabel(wxT("--")); + palette->SetLabel(wxT("---")); + } + } + } + protected: + u16 control, mode; + int frame, bg; + wxRadioButton *fr0, *fr1, *bg0, *bg1, *bg2, *bg3; + wxControl *modelab, *mapbase, *charbase, *size, *colors, *prio, *mosaic, + *overflow; + wxControl *coords, *addr, *tile, *flip, *palette; + int selx, sely; + + // following routines were copied from win32/MapView.cpp with little + // attempt to read & validate, except: + // stride = 1024, rgb instead of bgr + // FIXME: probably needs changing for big-endian + + void renderTextScreen() + { + u16 *palette = (u16 *)paletteRAM; + u8 *charBase = &vram[((control >> 2) & 0x03) * 0x4000]; + u16 *screenBase = (u16 *)&vram[((control >> 8) & 0x1f) * 0x800]; + u8 *bmp = image.GetData(); + + int sizeX = 256; + int sizeY = 256; + switch((control >> 14) & 3) { + case 0: + break; + case 1: + sizeX = 512; + break; + case 2: + sizeY = 512; + break; + case 3: + sizeX = 512; + sizeY = 512; + break; + } + + BMPSize(sizeX, sizeY); + + if(control & 0x80) { + for(int y = 0; y < sizeY; y++) { + int yy = y & 255; + + if(y == 256 && sizeY > 256) { + screenBase += 0x400; + if(sizeX > 256) + screenBase += 0x400; + } + u16 *screenSource = screenBase + ((yy>>3)*32); + + for(int x = 0; x < sizeX; x++) { + u16 data = *screenSource; + + int tile = data & 0x3FF; + int tileX = (x & 7); + int tileY = y & 7; + + if(data & 0x0400) + tileX = 7 - tileX; + if(data & 0x0800) + tileY = 7 - tileY; + + u8 c = charBase[tile * 64 + tileY * 8 + tileX]; + + u16 color = palette[c]; + + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + + if(data & 0x0400) { + if(tileX == 0) + screenSource++; + } else if(tileX == 7) + screenSource++; + if(x == 255 && sizeX > 256) { + screenSource = screenBase + 0x400 + ((yy>>3)*32); + } + } + bmp += 3 * (1024 - sizeX); + } + } else { + for(int y = 0; y < sizeY; y++) { + int yy = y & 255; + + if(y == 256 && sizeY > 256) { + screenBase += 0x400; + if(sizeX > 256) + screenBase += 0x400; + } + u16 *screenSource = screenBase + ((yy>>3)*32); + + for(int x = 0; x < sizeX; x++) { + u16 data = *screenSource; + + int tile = data & 0x3FF; + int tileX = (x & 7); + int tileY = y & 7; + + if(data & 0x0400) + tileX = 7 - tileX; + if(data & 0x0800) + tileY = 7 - tileY; + + u8 color = charBase[tile * 32 + tileY * 4 + (tileX>>1)]; + + if(tileX & 1) { + color = (color >> 4); + } else { + color &= 0x0F; + } + + int pal = (*screenSource>>8) & 0xF0; + u16 color2 = palette[pal + color]; + + *bmp++ = (color2 & 0x1f) << 3; + *bmp++ = ((color2 >> 5) & 0x1f) << 3; + *bmp++ = ((color2 >> 10) & 0x1f) << 3; + + if(data & 0x0400) { + if(tileX == 0) + screenSource++; + } else if(tileX == 7) + screenSource++; + + if(x == 255 && sizeX > 256) { + screenSource = screenBase + 0x400 + ((yy>>3)*32); + } + } + bmp += 3 * (1024 - sizeX); + } + } +#if 0 + switch(bg) { + case 0: + renderView(BG0HOFS<<8, BG0VOFS<<8, + 0x100, 0x000, + 0x000, 0x100, + (sizeX -1) <<8, + (sizeY -1) << 8, + true); + break; + case 1: + renderView(BG1HOFS<<8, BG1VOFS<<8, + 0x100, 0x000, + 0x000, 0x100, + (sizeX -1) <<8, + (sizeY -1) << 8, + true); + break; + case 2: + renderView(BG2HOFS<<8, BG2VOFS<<8, + 0x100, 0x000, + 0x000, 0x100, + (sizeX -1) <<8, + (sizeY -1) << 8, + true); + break; + case 3: + renderView(BG3HOFS<<8, BG3VOFS<<8, + 0x100, 0x000, + 0x000, 0x100, + (sizeX -1) <<8, + (sizeY -1) << 8, + true); + break; + } +#endif + } + + void renderRotScreen() + { + u16 *palette = (u16 *)paletteRAM; + u8 *charBase = &vram[((control >> 2) & 0x03) * 0x4000]; + u8 *screenBase = (u8 *)&vram[((control >> 8) & 0x1f) * 0x800]; + u8 *bmp = image.GetData(); + + int sizeX = 128; + int sizeY = 128; + switch((control >> 14) & 3) { + case 0: + break; + case 1: + sizeX = sizeY = 256; + break; + case 2: + sizeX = sizeY = 512; + break; + case 3: + sizeX = sizeY = 1024; + break; + } + + BMPSize(sizeX, sizeY); + + if(control & 0x80) { + for(int y = 0; y < sizeY; y++) { + for(int x = 0; x < sizeX; x++) { + int tile = screenBase[(x>>3) + (y>>3)*(sizeX>>3)]; + + int tileX = (x & 7); + int tileY = y & 7; + + u8 color = charBase[tile * 64 + tileY * 8 + tileX]; + u16 color2 = palette[color]; + + *bmp++ = (color2 & 0x1f) << 3; + *bmp++ = ((color2 >> 5) & 0x1f) << 3; + *bmp++ = ((color2 >> 10) & 0x1f) << 3; + } + } + bmp += 3 * (1024 - sizeX); + } else { + for(int y = 0; y < sizeY; y++) { + for(int x = 0; x < sizeX; x++) { + int tile = screenBase[(x>>3) + (y>>3)*(sizeX>>3)]; + + int tileX = (x & 7); + int tileY = y & 7; + + u8 color = charBase[tile * 64 + tileY * 8 + tileX]; + u16 color2 = palette[color]; + + *bmp++ = (color2 & 0x1f) << 3; + *bmp++ = ((color2 >> 5) & 0x1f) << 3; + *bmp++ = ((color2 >> 10) & 0x1f) << 3; + } + } + bmp += 3 * (1024 - sizeX); + } + + u32 xx; + u32 yy; + + switch(bg) { + case 2: + xx = BG2X_L | BG2X_H << 16; + yy = BG2Y_L | BG2Y_H << 16; + + +#if 0 + renderView(xx, yy, + BG2PA, BG2PC, + BG2PB, BG2PD, + (sizeX -1) <<8, + (sizeY -1) << 8, + (control & 0x2000) != 0); +#endif + break; + case 3: + xx = BG3X_L | BG3X_H << 16; + yy = BG3Y_L | BG3Y_H << 16; +#if 0 + renderView(xx, yy, + BG3PA, BG3PC, + BG3PB, BG3PD, + (sizeX -1) <<8, + (sizeY -1) << 8, + (control & 0x2000) != 0); +#endif + break; + } + } + + void renderMode3() + { + u8 *bmp = image.GetData(); + u16 *src = (u16 *)&vram[0]; + + BMPSize(240, 160); + + for(int y = 0; y < 160; y++) { + for(int x = 0; x < 240; x++) { + u16 data = *src++; + *bmp++ = (data & 0x1f) << 3; + *bmp++ = ((data >> 5) & 0x1f) << 3; + *bmp++ = ((data >> 10) & 0x1f) << 3; + } + bmp += 3 * (1024 - 240); + } + } + + + void renderMode4() + { + u8 *bmp = image.GetData(); + u8 *src = frame ? &vram[0xa000] : &vram[0]; + u16 *pal = (u16 *)&paletteRAM[0]; + + BMPSize(240, 160); + + for(int y = 0; y < 160; y++) { + for(int x = 0; x < 240; x++) { + u8 c = *src++; + u16 data = pal[c]; + *bmp++ = (data & 0x1f) << 3; + *bmp++ = ((data >> 5) & 0x1f) << 3; + *bmp++ = ((data >> 10) & 0x1f) << 3; + } + bmp += 3 * (1024 - 240); + } + } + + + void renderMode5() + { + u8 *bmp = image.GetData(); + u16 *src = (u16 *)(frame ? &vram[0xa000] : &vram[0]); + + BMPSize(160, 128); + + for(int y = 0; y < 128; y++) { + for(int x = 0; x < 160; x++) { + u16 data = *src++; + *bmp++ = (data & 0x1f) << 3; + *bmp++ = ((data >> 5) & 0x1f) << 3; + *bmp++ = ((data >> 10) & 0x1f) << 3; + } + bmp += 3 * (1024 - 160); + } + } + + DECLARE_EVENT_TABLE() + }; + + BEGIN_EVENT_TABLE(MapViewer, GfxViewer) + EVT_GFX_CLICK(wxID_ANY, MapViewer::UpdateMouseInfoEv) + END_EVENT_TABLE() + + class GBMapViewer : public GfxViewer + { + public: + GBMapViewer() : GfxViewer(wxT("GBMapViewer"), 256, 256) + { + getradio(,"CharBase0", charbase, 0x0000); + getradio(,"CharBase1", charbase, 0x0800); + getradio(,"MapBase0", mapbase, 0x1800); + getradio(,"MapBase1", mapbase, 0x1c00); + getlab(coords, "Coords", "(2WW,2WW)"); + getlab(addr, "Address", "0xWWWW"); + getlab(tile, "Tile", "2WW"); + getlab(flip, "Flip", "HV"); + getlab(palette, "Palette", "---"); + getlab(prio, "Priority", "P"); + Fit(); + selx = sely = -1; + Update(); + } + void Update() + { + u8 *bank0, *bank1; + if(gbCgbMode) { + bank0 = &gbVram[0x0000]; + bank1 = &gbVram[0x2000]; + } else { + bank0 = &gbMemory[0x8000]; + bank1 = NULL; + } + int tile_map_address = mapbase; + + // following copied almost verbatim from win32/GBMapView.cpp + int tile = 0; + for(int y = 0; y < 32; y++) { + for(int x = 0; x < 32; x++) { + u8 *bmp = &image.GetData()[y * 8 * 32 * 24 + x*24]; + u8 attrs = 0; + if(bank1 != NULL) + attrs = bank1[tile_map_address]; + u8 tile = bank0[tile_map_address]; + tile_map_address++; + + if(charbase) { + if(tile < 128) tile += 128; + else tile -= 128; + } + for(int j = 0; j < 8; j++) { + int charbase_address = attrs & 0x40 ? + charbase + tile*16 + (7-j)*2: + charbase + tile*16+j*2; + + u8 tile_a = 0; + u8 tile_b = 0; + + if(attrs & 0x08) { + tile_a = bank1[charbase_address++]; + tile_b = bank1[charbase_address]; + } else { + tile_a = bank0[charbase_address++]; + tile_b = bank0[charbase_address]; + } + + if(attrs & 0x20) { + tile_a = gbInvertTab[tile_a]; + tile_b = gbInvertTab[tile_b]; + } + + u8 mask = 0x80; + + while(mask > 0) { + u8 c = (tile_a & mask) ? 1 : 0; + c += (tile_b & mask) ? 2 : 0; + + if(gbCgbMode) + c = c + (attrs & 7)*4; + + u16 color = gbPalette[c]; + + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + + mask >>= 1; + } + bmp += 31*24; + } + } + } + ChangeBMP(); + UpdateMouseInfo(); + } + + void UpdateMouseInfoEv(wxMouseEvent &ev) + { + selx = ev.GetX(); + sely = ev.GetY(); + UpdateMouseInfo(); // note that this will be inaccurate if game + // not paused since last refresh + } + + void UpdateMouseInfo() + { + if(selx > gv->bmw || sely > gv->bmh) + selx = sely = -1; + if(selx < 0) { + coords->SetLabel(wxEmptyString); + addr->SetLabel(wxEmptyString); + tile->SetLabel(wxEmptyString); + flip->SetLabel(wxEmptyString); + palette->SetLabel(wxEmptyString); + prio->SetLabel(wxEmptyString); + } else { + wxString s; + s.Printf(wxT("(%d,%d)"), selx, sely); + coords->SetLabel(s); + u16 address = mapbase + 0x8000 + (sely >> 3) * 32 + (selx >> 3); + s.Printf(wxT("0x%04X"), address); + addr->SetLabel(s); + u8 attrs = 0; + u8 tilev = gbMemoryMap[9][address & 0xfff]; + if(gbCgbMode) { + attrs = gbVram[0x2000 + address - 0x8000]; + tilev = gbVram[address & 0x1fff]; + } + if(charbase) { + if(tilev >= 128) + tilev -= 128; + else + tilev += 128; + } + s.Printf(wxT("%d"), (int)tilev); + tile->SetLabel(s); + s = attrs & 0x20 ? wxT('H') : wxT('-'); + s += attrs & 0x40 ? wxT('V') : wxT('-'); + flip->SetLabel(s); + if(gbCgbMode) { + s.Printf(wxT("%d"), attrs & 7); + palette->SetLabel(s); + } else + palette->SetLabel(wxT("---")); + prio->SetLabel(wxString(attrs & 0x80 ? wxT('P') : wxT('-'))); + } + } + protected: + int charbase, mapbase; + wxControl *coords, *addr, *tile, *flip, *palette, *prio; + int selx, sely; + + DECLARE_EVENT_TABLE() + }; + + BEGIN_EVENT_TABLE(GBMapViewer, GfxViewer) + EVT_GFX_CLICK(wxID_ANY, GBMapViewer::UpdateMouseInfoEv) + END_EVENT_TABLE() +} + +void MainFrame::MapViewer() +{ + switch(panel->game_type()) { + case IMAGE_GBA: + LoadXRCViewer(Map); + break; + case IMAGE_GB: + LoadXRCViewer(GBMap); + break; + } +} + +namespace Viewers { + class OAMViewer : public GfxViewer + { + public: + OAMViewer() : GfxViewer(wxT("OAMViewer"), 64, 64) + { + sprite = 0; + getspin(,"Sprite", sprite); + getlab(pos, "Pos", "5WW,2WW"); + getlab(mode, "Mode", "3"); + getlab(colors, "Colors", "256"); + getlab(pallab, "Palette", "1W"); + getlab(tile, "Tile", "1WWW"); + getlab(prio, "Priority", "3"); + getlab(size, "Size", "64x64"); + getlab(rot, "Rotation", "3W"); + getlab(flg, "Flags", "RHVMD"); + Fit(); + Update(); + } + void Update() + { + u16 *sparms = &((u16 *)oam)[4 * sprite]; + u16 a0 = sparms[0], a1 = sparms[1], a2 = sparms[2]; + u16 *pal = &((u16 *)paletteRAM)[0x100]; + u8 *bmp = image.GetData(); + + int sizeX = 8, sizeY = 8; + + // following is almost verbatim from OamView.cpp + // shape = (a0 >> 14) & 3; + // size = (a1 >> 14) & 3; + switch(((a0 >> 12) & 0xc) | (a1 >> 14)) { + case 0: + break; + case 1: + sizeX = sizeY = 16; + break; + case 2: + sizeX = sizeY = 32; + break; + case 3: + sizeX = sizeY = 64; + break; + case 4: + sizeX = 16; + break; + case 5: + sizeX = 32; + break; + case 6: + sizeX = 32; + sizeY = 16; + break; + case 7: + sizeX = 64; + sizeY = 32; + break; + case 8: + sizeY = 16; + break; + case 9: + sizeY = 32; + break; + case 10: + sizeX = 16; + sizeY = 32; + break; + case 11: + sizeX = 32; + sizeY = 64; + break; + default: + pos->SetLabel(wxEmptyString); + mode->SetLabel(wxEmptyString); + colors->SetLabel(wxEmptyString); + pallab->SetLabel(wxEmptyString); + tile->SetLabel(wxEmptyString); + prio->SetLabel(wxEmptyString); + size->SetLabel(wxEmptyString); + rot->SetLabel(wxEmptyString); + flg->SetLabel(wxEmptyString); + BMPSize(sizeX, sizeY); + memset(bmp, 0, 8 * 8 * 3); + ChangeBMP(); + return; + } + BMPSize(sizeX, sizeY); + + int sy = (a0 & 255); + + if(a0 & 0x2000) { + int c = (a2 & 0x3FF); + //if((DISPCNT & 7) > 2 && (c < 512)) + // return; + int inc = 32; + if(DISPCNT & 0x40) + inc = sizeX >> 2; + else + c &= 0x3FE; + + for(int y = 0; y < sizeY; y++) { + for(int x = 0; x < sizeX; x++) { + u32 color = vram[0x10000 + (((c + (y>>3) * inc)* + 32 + (y & 7) * 8 + (x >> 3) * 64 + + (x & 7))&0x7FFF)]; + color = pal[color]; + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + } + bmp += (64 - sizeX) * 3; + } + } else { + int c = (a2 & 0x3FF); + //if((DISPCNT & 7) > 2 && (c < 512)) + // return; + int inc = 32; + if(DISPCNT & 0x40) + inc = sizeX >> 3; + int palette = (a2 >> 8) & 0xF0; + for(int y = 0; y < sizeY; y++) { + for(int x = 0; x < sizeX; x++) { + u32 color = vram[0x10000 + (((c + (y>>3) * inc)* + 32 + (y & 7) * 4 + (x >> 3) * 32 + + ((x & 7)>>1))&0x7FFF)]; + if(x & 1) + color >>= 4; + else + color &= 0x0F; + + color = pal[palette+color]; + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + } + bmp += (64 - sizeX) * 3; + } + } + ChangeBMP(); + + wxString s; + s.Printf(wxT("%d,%d"), a1 & 511, a0 & 255); + pos->SetLabel(s); + s.Printf(wxT("%d"), (a0 >> 10) & 3); + mode->SetLabel(s); + colors->SetLabel(a0 & 8192 ? wxT("256") : wxT("16")); + s.Printf(wxT("%d"), (a2 >> 12) & 15); + pallab->SetLabel(s); + s.Printf(wxT("%d"), a2 & 1023); + tile->SetLabel(s); + s.Printf(wxT("%d"), (a2 >> 10) & 3); + prio->SetLabel(s); + s.Printf(wxT("%dx%d"), sizeX, sizeY); + size->SetLabel(s); + if(a0 & 512) { + s.Printf(wxT("%d"), (a1 >> 9) & 31); + rot->SetLabel(s); + } else + rot->SetLabel(wxEmptyString); + s = wxEmptyString; + if(a0 & 512) + s.append(wxT("R--")); + else { + s.append(wxT('-')); + s.append(a1 & 4096 ? wxT('H') : wxT('-')); + s.append(a1 & 8192 ? wxT('V') : wxT('-')); + } + s.append(a0 & 4096 ? wxT('M') : wxT('-')); + s.append(a0 & 1024 ? wxT('D') : wxT('-')); + flg->SetLabel(s); + } + protected: + int sprite; + wxControl *pos, *mode, *colors, *pallab, *tile, *prio, *size, *rot, *flg; + }; + + class GBOAMViewer : public GfxViewer + { + public: + GBOAMViewer() : GfxViewer(wxT("GBOAMViewer"), 8, 16) + { + sprite = 0; + getspin(,"Sprite", sprite); + getlab(pos, "Pos", "2WW,2WW"); + getlab(tilelab, "Tile", "2WW"); + getlab(prio, "Priority", "W"); + getlab(oap, "OAP", "W"); + getlab(pallab, "Palette", "W"); + getlab(flg, "Flags", "HV"); + getlab(banklab, "Bank", "W"); + Fit(); + Update(); + } + void Update() + { + u8 *bmp = image.GetData(); + + // following is almost verbatim from GBOamView.cpp + u16 addr = sprite * 4 + 0xfe00; + + int size = register_LCDC & 4; + + u8 y = gbMemory[addr++]; + u8 x = gbMemory[addr++]; + u8 tile = gbMemory[addr++]; + if(size) + tile &= 254; + u8 flags = gbMemory[addr++]; + + int w = 8; + int h = size ? 16 : 8; + BMPSize(w, h); + + u8 * bank0; + u8 * bank1; + if(gbCgbMode) { + if(register_VBK & 1) { + bank0 = &gbVram[0x0000]; + bank1 = &gbVram[0x2000]; + } else { + bank0 = &gbVram[0x0000]; + bank1 = &gbVram[0x2000]; + } + } else { + bank0 = &gbMemory[0x8000]; + bank1 = NULL; + } + + int init = 0x0000; + + u8 *pal = gbObp0; + + if((flags & 0x10)) + pal = gbObp1; + + for(int yy = 0; yy < h; yy++) { + int address = init + tile * 16 + 2*yy; + int a = 0; + int b = 0; + + if(gbCgbMode && flags & 0x08) { + a = bank1[address++]; + b = bank1[address++]; + } else { + a = bank0[address++]; + b = bank0[address++]; + } + + for(int xx = 0; xx < 8; xx++) { + u8 mask = 1 << (7-xx); + u8 c = 0; + if( (a & mask)) + c++; + if( (b & mask)) + c+=2; + + // make sure that sprites will work even in CGB mode + if(gbCgbMode) { + c = c + (flags & 0x07)*4 + 32; + } else { + c = pal[c]; + } + + u16 color = gbPalette[c]; + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + } + } + ChangeBMP(); + + wxString s; + s.Printf(wxT("%d,%d"), x, y); + pos->SetLabel(s); + s.Printf(wxT("%d"), tile); + tilelab->SetLabel(s); + prio->SetLabel(flags & 0x80 ? wxT("1") : wxT("0")); + oap->SetLabel(flags & 0x08 ? wxT("1") : wxT("0")); + s.Printf(wxT("%d"), flags & 7); + pallab->SetLabel(s); + s = flags & 0x20 ? wxT('H') : wxT('-'); + s.append(flags & 0x40 ? wxT('V') : wxT('-')); + flg->SetLabel(s); + banklab->SetLabel(flags & 0x10 ? wxT("1") : wxT("0")); + } + protected: + int sprite; + wxControl *pos, *tilelab, *prio, *oap, *pallab, *flg, *banklab; + }; +} + +void MainFrame::OAMViewer() +{ + switch(panel->game_type()) { + case IMAGE_GBA: + LoadXRCViewer(OAM); + break; + case IMAGE_GB: + LoadXRCViewer(GBOAM); + break; + } +} + +namespace Viewers { + static int ptype = 0; + static wxString pdir; + void savepal(wxWindow *parent, const u8 *data, int ncols, const wxChar *type) + { + // no attempt is made here to translate the palette type name + // it's just a suggested name, anyway + wxString def_name = wxGetApp().frame->GetPanel()->game_name() + + wxT('-') + type; + if(ptype == 2) + def_name += wxT(".act"); + else + def_name += wxT(".pal"); + wxFileDialog dlg(parent, _("Select output file and type"), pdir, def_name, + _("Windows Palette (*.pal)|*.pal|PaintShop Palette (*.pal)|*.pal|Adobe Color Table (*.act)|*.act"), + wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + dlg.SetFilterIndex(ptype); + int ret = dlg.ShowModal(); + ptype = dlg.GetFilterIndex(); + pdir = dlg.GetDirectory(); + if(ret != wxID_OK) + return; + wxFFile f(dlg.GetPath(), wxT("wb")); + // FIXME: check for errors + switch(ptype) { + case 0: // Windows palette + { + f.Write("RIFF", 4); + u32 d = wxUINT32_SWAP_ON_BE(256 * 4 + 16); + f.Write(&d, 4); + f.Write("PAL data", 8); + d = wxUINT32_SWAP_ON_BE(256 * 4 + 4); + f.Write(&d, 4); + u16 w = wxUINT16_SWAP_ON_BE(0x0300); + f.Write(&w, 2); + w = wxUINT16_SWAP_ON_BE(256); // cuases problems if not 16 or 256 + f.Write(&w, 2); + for(int i = 0; i < ncols; i++, data += 3) { + f.Write(data, 3); + u8 z = 0; + f.Write(&z, 1); + } + for(int i = ncols; i < 256; i++) { + d = 0; + f.Write(&d, 4); + } + } + break; + case 1: // PaintShop palette + { +#define jasc_head "JASC-PAL\r\n0100\r\n256\r\n" + f.Write(jasc_head, sizeof(jasc_head) - 1); + for(int i = 0; i < ncols; i++, data += 3) { + char buf[14]; + int l = sprintf(buf, "%d %d %d\r\n", data[0], data[1], data[2]); + f.Write(buf, l); + } + for(int i = ncols; i < 256; i++) + f.Write("0 0 0\r\n", 7); + break; + } + case 2: // Adobe color table + { + f.Write(data, ncols * 3); + u32 d = 0; + for(int i = ncols; i < 256; i++) + f.Write(&d, 3); + } + break; + } + f.Close(); // FIXME: check for errors + } + + class PaletteViewer : public Viewer + { + public: + PaletteViewer() : Viewer(wxT("PaletteViewer")) + { + colorctrl(cv, "Color"); + pixview(bpv, "Background", 16, 16, cv); + pixview(spv, "Sprite", 16, 16, cv); + getlab(addr, "Address", "0x5000WWW"); + getlab(val, "Value", "0xWWWW"); + Fit(); + Update(); + } + void Update() + { + if(paletteRAM) { + u16 *pp = (u16 *)paletteRAM; + u8 *bmp = colbmp; + for(int i = 0; i < 512; i++, pp++) { + *bmp++ = (*pp & 0x1f) << 3; + *bmp++ = (*pp & 0x3e0) >> 2; + *bmp++ = (*pp & 0x7c00) >> 7; + } + } else + memset(colbmp, 0, sizeof(colbmp)); + bpv->SetData(colbmp, 16, 0, 0); + spv->SetData(colbmp + 16*16*3, 16, 0, 0); + ShowSel(); + } + void SelBG(wxMouseEvent &ev) + { + spv->SetSel(-1, -1, false); + ShowSel(); + } + void SelSprite(wxMouseEvent &ev) + { + bpv->SetSel(-1, -1, false); + ShowSel(); + } + void ShowSel() + { + int x, y; + bool isbg = true; + bpv->GetSel(x, y); + if(x < 0) { + isbg = false; + spv->GetSel(x, y); + if(x < 0) { + addr->SetLabel(wxEmptyString); + val->SetLabel(wxEmptyString); + return; + } + } + int off = x + y * 16; + if(!isbg) + off += 16 * 16; + u8 *pix = &colbmp[off * 3]; + u16 v = (pix[0] >> 3) + ((pix[1] >> 3) << 5) + ((pix[2] >> 3) << 10); + wxString s; + s.Printf(wxT("0x%04X"), (int)v); + val->SetLabel(s); + s.Printf(wxT("0x%08X"), 0x5000000 + 2 * off); + addr->SetLabel(s); + } + void SaveBG(wxCommandEvent &ev) + { + savepal(this, colbmp, 16 * 16, wxT("bg")); + } + void SaveOBJ(wxCommandEvent &ev) + { + savepal(this, colbmp + 16 * 16 * 3, 16 * 16, wxT("obj")); + } + void ChangeBackdrop(wxCommandEvent &ev) + { + // FIXME: this should really be a preference + // should also have some way of indicating selection + // perhaps replace w/ checkbox + colorpickerctrl + static wxColourData *cd = NULL; + wxColourDialog dlg(this, cd); + if(dlg.ShowModal() == wxID_OK) { + if(!cd) + cd = new wxColourData(); + *cd = dlg.GetColourData(); + wxColour c = cd->GetColour(); + customBackdropColor = + (c.Red() >> 3) + + (c.Green() >> 3) << 5 + + (c.Blue() >> 3) << 10; + } else + // kind of an unintuitive way to turn it off... + customBackdropColor = -1; + } + protected: + ColorView *cv; + PixView *bpv, *spv; + u8 colbmp[16*16*3*2]; + wxControl *addr, *val; + + DECLARE_EVENT_TABLE() + }; + + BEGIN_EVENT_TABLE(PaletteViewer, Viewer) + EVT_BUTTON(XRCID("SaveBG"), PaletteViewer::SaveBG) + EVT_BUTTON(XRCID("SaveOBJ"), PaletteViewer::SaveOBJ) + EVT_BUTTON(XRCID("ChangeBackdrop"), PaletteViewer::ChangeBackdrop) + EVT_GFX_CLICK(XRCID("Background"), PaletteViewer::SelBG) + EVT_GFX_CLICK(XRCID("Sprite"), PaletteViewer::SelSprite) + END_EVENT_TABLE() + + class GBPaletteViewer : public Viewer + { + public: + GBPaletteViewer() : Viewer(wxT("GBPaletteViewer")) + { + colorctrl(cv, "Color"); + pixview(bpv, "Background", 4, 8, cv); + pixview(spv, "Sprite", 4, 8, cv); + getlab(idx, "Index", "3W"); + getlab(val, "Value", "0xWWWW"); + Fit(); + Update(); + } + void Update() + { + u16 *pp = gbPalette; + u8 *bmp = colbmp; + for(int i = 0; i < 64; i++, pp++) { + *bmp++ = (*pp & 0x1f) << 3; + *bmp++ = (*pp & 0x3e0) >> 2; + *bmp++ = (*pp & 0x7c00) >> 7; + } + bpv->SetData(colbmp, 4, 0, 0); + spv->SetData(colbmp + 4*8*3, 4, 0, 0); + ShowSel(); + } + void SelBG(wxMouseEvent &ev) + { + spv->SetSel(-1, -1, false); + ShowSel(); + } + void SelSprite(wxMouseEvent &ev) + { + bpv->SetSel(-1, -1, false); + ShowSel(); + } + void ShowSel() + { + int x, y; + bool isbg = true; + bpv->GetSel(x, y); + if(x < 0) { + isbg = false; + spv->GetSel(x, y); + if(x < 0) { + idx->SetLabel(wxEmptyString); + val->SetLabel(wxEmptyString); + return; + } + } + u8 *pix = &colbmp[(x + y * 4) * 3]; + if(isbg) + pix += 4 * 8 * 3; + u16 v = (pix[0] >> 3) + ((pix[1] >> 3) << 5) + ((pix[2] >> 3) << 10); + wxString s; + s.Printf(wxT("0x%04X"), (int)v); + val->SetLabel(s); + s.Printf(wxT("%d"), x + y * 4); + idx->SetLabel(s); + } + void SaveBG(wxCommandEvent &ev) + { + savepal(this, colbmp, 4 * 8, wxT("bg")); + } + void SaveOBJ(wxCommandEvent &ev) + { + savepal(this, colbmp + 4 * 8 * 3, 4 * 8, wxT("obj")); + } + protected: + ColorView *cv; + PixView *bpv, *spv; + u8 colbmp[4*8*3*2]; + wxControl *idx, *val; + DECLARE_EVENT_TABLE() + }; + + BEGIN_EVENT_TABLE(GBPaletteViewer, Viewer) + EVT_BUTTON(XRCID("SaveBG"), GBPaletteViewer::SaveBG) + EVT_BUTTON(XRCID("SaveOBJ"), GBPaletteViewer::SaveOBJ) + EVT_GFX_CLICK(XRCID("Background"), GBPaletteViewer::SelBG) + EVT_GFX_CLICK(XRCID("Sprite"), GBPaletteViewer::SelSprite) + END_EVENT_TABLE() +} + +void MainFrame::PaletteViewer() +{ + switch(panel->game_type()) { + case IMAGE_GBA: + LoadXRCViewer(Palette); + break; + case IMAGE_GB: + LoadXRCViewer(GBPalette); + break; + } +} + +namespace Viewers { + class TileViewer : public GfxViewer + { + public: + TileViewer() : GfxViewer(wxT("TileViewer"), 32*8, 32*8) + { + is256 = charbase = 0; + getradio(,"Color16", is256, 0); + getradio(,"Color256", is256, 1); + getradio(,"CharBase0", charbase, 0); + getradio(,"CharBase1", charbase, 0x4000); + getradio(,"CharBase2", charbase, 0x8000); + getradio(,"CharBase3", charbase, 0xc000); + getradio(,"CharBase4", charbase, 0x10000); + getslider(,"Palette", palette); + getlab(tileno, "Tile", "1WWW"); + getlab(addr, "Address", "06WWWWWW"); + selx = sely = -1; + Fit(); + Update(); + } + void Update() + { + // Following copied almost verbatim from TileView.cpp + u16 *palette = (u16 *)paletteRAM; + u8 *charBase = &vram[charbase]; + + int maxY; + + if(is256) { + int tile = 0; + maxY = 16; + for(int y = 0; y < maxY; y++) { + for(int x = 0; x < 32; x++) { + if(charbase == 4 * 0x4000) + render256(tile, x, y, charBase, &palette[256]); + else + render256(tile, x, y, charBase, palette); + tile++; + } + } + BMPSize(32*8, maxY*8); + } else { + int tile = 0; + maxY = 32; + if(charbase == 3 * 0x4000) + maxY = 16; + for(int y = 0; y < maxY; y++) { + for(int x = 0; x < 32; x++) { + render16(tile, x, y, charBase, palette); + tile++; + } + } + BMPSize(32*8, maxY*8); + } + ChangeBMP(); + UpdateMouseInfo(); + } + void UpdateMouseInfoEv(wxMouseEvent &ev) + { + selx = ev.GetX(); + sely = ev.GetY(); + UpdateMouseInfo(); + } + + void UpdateMouseInfo() + { + if(selx > gv->bmw || sely > gv->bmh) + selx = sely = -1; + if(selx < 0) { + addr->SetLabel(wxEmptyString); + tileno->SetLabel(wxEmptyString); + } else { + int x = selx / 8; + int y = sely / 8; + int t = 32 * y + x; + if(is256) + t *= 2; + wxString s; + s.Printf(wxT("%d"), t); + tileno->SetLabel(s); + s.Printf(wxT("%08X"), 0x6000000 + charbase + 32 * t); + addr->SetLabel(s); + } + } + // following 2 functions copied almost verbatim from TileView.cpp + void render256(int tile, int x, int y, u8 *charBase, u16 *palette) + { + u8 *bmp = &image.GetData()[24*x + 8*32*24*y]; + + for(int j = 0; j < 8; j++) { + for(int i = 0; i < 8; i++) { + u8 c = charBase[tile*64 + j * 8 + i]; + + u16 color = palette[c]; + + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + + } + bmp += 31*24; // advance line + } + } + + void render16(int tile, int x, int y, u8 *charBase, u16 *palette) + { + u8 *bmp = &image.GetData()[24*x + 8*32*24*y]; + + int pal = this->palette; + + if(this->charbase == 4 * 0x4000) + pal += 16; + + for(int j = 0; j < 8; j++) { + for(int i = 0; i < 8; i++) { + u8 c = charBase[tile*32 + j * 4 + (i>>1)]; + + if(i & 1) + c = c>>4; + else + c = c & 15; + + u16 color = palette[pal*16+c]; + + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + } + bmp += 31*24; // advance line + } + } + + protected: + int charbase, is256, palette; + wxControl *tileno, *addr; + int selx, sely; + + DECLARE_EVENT_TABLE() + }; + + BEGIN_EVENT_TABLE(TileViewer, GfxViewer) + EVT_GFX_CLICK(wxID_ANY, TileViewer::UpdateMouseInfoEv) + END_EVENT_TABLE() + + class GBTileViewer : public GfxViewer + { + public: + GBTileViewer() : GfxViewer(wxT("GBTileViewer"), 16*8, 16*8) + { + bank = charbase = 0; + getradio(,"Bank0", bank, 0); + getradio(,"Bank1", bank, 0x2000); + getradio(,"CharBase0", charbase, 0); + getradio(,"CharBase1", charbase, 0x800); + getslider(,"Palette", palette); + getlab(tileno, "Tile", "2WW"); + getlab(addr, "Address", "WWWW"); + selx = sely = -1; + Fit(); + Update(); + } + void Update() + { + // following copied almost verbatim from GBTileView.cpp + u8 *charBase = (gbVram != NULL) ? + &gbVram[bank+charbase] : + &gbMemory[0x8000+charbase]; + + int tile = 0; + for(int y = 0; y < 16; y++) { + for(int x = 0; x < 16; x++) { + render(tile, x, y, charBase); + tile++; + } + } + ChangeBMP(); + UpdateMouseInfo(); + } + void UpdateMouseInfoEv(wxMouseEvent &ev) + { + selx = ev.GetX(); + sely = ev.GetY(); + UpdateMouseInfo(); + } + + void UpdateMouseInfo() + { + if(selx > gv->bmw || sely > gv->bmh) + selx = sely = -1; + if(selx < 0) { + addr->SetLabel(wxEmptyString); + tileno->SetLabel(wxEmptyString); + } else { + int x = selx / 8; + int y = sely / 8; + int t = 16 * y + x; + wxString s; + s.Printf(wxT("%d"), t); + tileno->SetLabel(s); + s.Printf(wxT("%04X"), 0x8000 + charbase + 16 * t); + addr->SetLabel(s); + } + } + + // following function copied almost verbatim from GBTileView.cpp + void render(int tile, int x, int y, u8 *charBase) + { + u8 *bmp = &image.GetData()[24*x + 8*16*24*y]; + + for(int j = 0; j < 8; j++) { + u8 mask = 0x80; + u8 tile_a = charBase[tile*16+j*2]; + u8 tile_b = charBase[tile*16+j*2+1]; + + for(int i = 0; i < 8; i++) { + u8 c = (tile_a & mask) ? 1 : 0; + c += ((tile_b & mask) ? 2 : 0); + + if(gbCgbMode) { + c = c + palette*4; + } else { + c = gbBgp[c]; + } + + u16 color = gbPalette[c]; + + *bmp++ = (color & 0x1f) << 3; + *bmp++ = ((color >> 5) & 0x1f) << 3; + *bmp++ = ((color >> 10) & 0x1f) << 3; + + mask >>= 1; + } + bmp += 15*24; // advance line + } + } + protected: + int bank, charbase, palette; + wxControl *addr, *tileno; + int selx, sely; + + DECLARE_EVENT_TABLE() + }; + + BEGIN_EVENT_TABLE(GBTileViewer, GfxViewer) + EVT_GFX_CLICK(wxID_ANY, GBTileViewer::UpdateMouseInfoEv) + END_EVENT_TABLE() +} + +void MainFrame::TileViewer() +{ + switch(panel->game_type()) { + case IMAGE_GBA: + LoadXRCViewer(Tile); + break; + case IMAGE_GB: + LoadXRCViewer(GBTile); + break; + } +} diff --git a/src/wx/guiinit.cpp b/src/wx/guiinit.cpp new file mode 100644 index 00000000..200347c0 --- /dev/null +++ b/src/wx/guiinit.cpp @@ -0,0 +1,3309 @@ + +// initialize menus & dialogs, etc. +// for most of the prefs dialogs, all code resides here in the form of +// event handlers & validators +// other non-viewer dialogs are at least validated enough that they won't crash +// viewer dialogs are not commonly used, so they are initialized on demand + +#include "wxvbam.h" + +#include +#include +#include +#include +#include +#include +#include "wx/checkedlistctrl.h" +#include +#include +#include "../gba/CheatSearch.h" + +// The program icon, in case it's missing from .xrc (MSW gets it from .rc file) +#if !defined(__WXMSW__) && !defined(__WXPM__) +// ImageMagick makes the name wxvbam, but wx expects wxvbam_xpm +#define wxvbam wxvbam_xpm +const +#include "wxvbam.xpm" +#undef wxvbam +#endif + +// this is supposed to happen automatically if a parent is marked recursive +// but some dialogs don't do it (propertydialog?) +// so go ahead and mark all dialogs for fully recursive validation +static void mark_recursive(wxWindowBase *w) +{ + w->SetExtraStyle(w->GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY); + wxWindowList l = w->GetChildren(); + for(wxWindowList::iterator ch = l.begin(); ch != l.end(); ch++) + mark_recursive(*ch); +} + +#define GetXRCDialog(n) \ + wxStaticCast(wxGetApp().frame->FindWindow(XRCID(n)), wxDialog) + +// Event handlers must be methods of wxEvtHandler-derived objects + +// manage the network link dialog +#ifndef NO_LINK +static class NetLink_t : public wxEvtHandler +{ +public: + wxDialog *dlg; + int n_players; + NetLink_t() : n_players(2) {} + wxButton *okb; + void ServerOKButton(wxCommandEvent &ev) + { + okb->SetLabel(_("Start!")); + } + void ClientOKButton(wxCommandEvent &ev) + { + okb->SetLabel(_("Connect")); + } + // attached to OK, so skip when OK + void NetConnect(wxCommandEvent &ev) + { + if(!dlg->Validate() || !dlg->TransferDataFromWindow()) + return; + update_opts(); // save fast flag and client host + + wxString connmsg, pmsg; + + wxMutex lock; + wxCondition sig(lock); + lock.Lock(); + + bool done = false; + + if(lanlink.server) { + lanlink.numslaves = n_players - 1; + class sid_t : public ServerInfoDisplay + { + wxMutex *lock; + wxCondition *sig; + wxString *connmsg, *pmsg; + bool *done; + bool conn[3]; + public: + sid_t(wxMutex *m, wxCondition *c, wxString *cm, wxString *pm, + bool *d) : + lock(m), sig(c), connmsg(cm), pmsg(pm), done(d) {} + void ShowServerIP(sf::IPAddress addr) { + wxString addr_s(addr.ToString().c_str(), wxConvLibc); + wxString msg; + msg.Printf(_("Server IP address is: %s\n"), addr_s.c_str()); + connmsg->append(msg); + conn[0] = conn[1] = conn[2] = false; + } + void ShowConnect(int player) { + wxString msg; + conn[player - 1] = true; + lock->Lock(); + pmsg->clear(); + for(int i = 0; i < 3; i++) + if(conn[i]) { + msg.Printf(_("Player %d connected\n"), i + 2); + pmsg->append(msg); + } + sig->Signal(); + lock->Unlock(); + } + void Ping() { + lock->Lock(); + sig->Signal(); + if(*done) + lanlink.terminate = true; + lock->Unlock(); + } + void Connected() { + lock->Lock(); + *done = true; + sig->Signal(); + lock->Unlock(); + } + } sid(&lock, &sig, &connmsg, &pmsg, &done); + if(!ls.Init(&sid)) { + wxLogError(_("Error occurred.\nPlease try again.")); + lock.Unlock(); + return; + } + wxProgressDialog + pdlg(_("Waiting for clients..."), connmsg, + 100, dlg, wxPD_APP_MODAL|wxPD_CAN_ABORT|wxPD_ELAPSED_TIME); + while(!done) { + if(!pdlg.Pulse(connmsg + pmsg)) + done = true; + sig.Wait(); + } + } else { + class cid_t : public ClientInfoDisplay + { + wxMutex *lock; + wxCondition *sig; + wxString *connmsg, *pmsg; + bool *done; + public: + cid_t(wxMutex *m, wxCondition *c, wxString *cm, wxString *pm, + bool *d) : + lock(m), sig(c), connmsg(cm), pmsg(pm), done(d) {} + void ConnectStart(sf::IPAddress addr) { + wxString addr_s(addr.ToString().c_str(), wxConvLibc); + connmsg->Printf(_("Connecting to %s\n"), addr_s.c_str()); + } + void ShowConnect(int player, int togo) { + wxString msg; + lock->Lock(); + pmsg->Printf(_("Connected as #%d\n"), player); + if(togo) + msg.Printf(_("Waiting for %d players to join"), togo); + else + msg = _("All players joined."); + pmsg->append(msg); + sig->Signal(); + lock->Unlock(); + } + void Ping() { + lock->Lock(); + sig->Signal(); + if(*done) + lanlink.terminate = true; + lock->Unlock(); + } + void Connected() { + lock->Lock(); + *done = true; + sig->Signal(); + lock->Unlock(); + } + } cid(&lock, &sig, &connmsg, &pmsg, &done); + int err; + if((err = lc.Init(sf::IPAddress(std::string(gopts.link_host.mb_str())), + &cid))) { + wxLogError(_("Error %d occurred.\nPlease try again."), err); + lock.Unlock(); + return; + } + wxProgressDialog + pdlg(_("Waiting for connection..."), connmsg, + 100, dlg, wxPD_APP_MODAL|wxPD_CAN_ABORT|wxPD_ELAPSED_TIME); + while(!done) { + if(!pdlg.Pulse(connmsg + pmsg)) + done = true; + sig.Wait(); + } + } + lock.Unlock(); + if(lanlink.connected) { + pmsg.Replace(wxT("\n"), wxT(" ")); + systemScreenMessage(pmsg); + lanlink.active = true; + ev.Skip(); // all OK + } + } +} net_link_handler; +#endif + +// manage the cheat list dialog +static class CheatList_t : public wxEvtHandler +{ +public: + wxDialog *dlg; + wxCheckedListCtrl *list; + wxListItem item0, item1; + int col1minw; + wxString cheatdir, cheatfn, deffn; + bool isgb; + bool *dirty; + + // add/edit dialog + wxString ce_desc; + wxString ce_codes; + wxChoice *ce_type_ch; + wxControl *ce_codes_tc; + int ce_type; + + void Reload() + { + list->DeleteAllItems(); + Reload(0); + } + + void Reload(int start) + { + if(isgb) { + for(int i = start; i < gbCheatNumber; i++) { + item0.SetId(i); + item0.SetText(wxString(gbCheatList[i].cheatCode, wxConvLibc)); + list->InsertItem(item0); + item1.SetId(i); + item1.SetText(wxString(gbCheatList[i].cheatDesc, wxConvUTF8)); + list->SetItem(item1); + list->Check(i, gbCheatList[i].enabled); + } + } else { + for(int i = start; i < cheatsNumber; i++) { + item0.SetId(i); + item0.SetText(wxString(cheatsList[i].codestring, wxConvLibc)); + list->InsertItem(item0); + item1.SetId(i); + item1.SetText(wxString(cheatsList[i].desc, wxConvUTF8)); + list->SetItem(item1); + list->Check(i, cheatsList[i].enabled); + } + } + AdjustDescWidth(); + } + + void Tool(wxCommandEvent &ev) + { + switch(ev.GetId()) { + case wxID_OPEN: + { + wxFileDialog subdlg(dlg, _("Select cheat file"), cheatdir, + cheatfn, _("VBA cheat lists (*.clt)|*.clt"), + wxFD_OPEN|wxFD_FILE_MUST_EXIST); + int ret = subdlg.ShowModal(); + cheatdir = subdlg.GetDirectory(); + cheatfn = subdlg.GetPath(); + if(ret != wxID_OK) + break; + bool cld; + if(isgb) + cld = gbCheatsLoadCheatList(cheatfn.mb_fn_str()); + else + cld = cheatsLoadCheatList(cheatfn.mb_fn_str()); + if(cld) { + *dirty = cheatfn != deffn; + systemScreenMessage(_("Loaded cheats")); + } else + *dirty = true; // attempted load always clears + Reload(); + } + break; + case wxID_SAVE: + { + wxFileDialog subdlg(dlg, _("Select cheat file"), cheatdir, + cheatfn, _("VBA cheat lists (*.clt)|*.clt"), + wxFD_SAVE|wxFD_OVERWRITE_PROMPT); + int ret = subdlg.ShowModal(); + cheatdir = subdlg.GetDirectory(); + cheatfn = subdlg.GetPath(); + if(ret != wxID_OK) + break; + // note that there is no way to test for succes of save + if(isgb) + gbCheatsSaveCheatList(cheatfn.mb_fn_str()); + else + cheatsSaveCheatList(cheatfn.mb_fn_str()); + if(cheatfn == deffn) + *dirty = false; + systemScreenMessage(_("Saved cheats")); + } + break; + case wxID_ADD: + { + int ncheats = isgb ? gbCheatNumber : cheatsNumber; + ce_codes = wxEmptyString; + wxDialog *subdlg = GetXRCDialog("CheatEdit"); + subdlg->ShowModal(); + AddCheat(); + Reload(ncheats); + } + break; + case wxID_REMOVE: + { + bool asked = false, restore; + for(int i = list->GetItemCount() - 1; i >= 0; i--) + if(list->GetItemState(i, wxLIST_STATE_SELECTED)) { + list->DeleteItem(i); + if(isgb) + gbCheatRemove(i); + else { + if(!asked) { + asked = true; + restore = wxMessageBox(_("Restore old values?"), + _("Removing cheats"), + wxYES_NO|wxICON_QUESTION) == wxYES; + } + cheatsDelete(i, restore); + } + } + } + break; + case wxID_CLEAR: + if(isgb) { + if(gbCheatNumber) { + *dirty = true; + gbCheatRemoveAll(); + } + } else { + if(cheatsNumber) { + bool restore = wxMessageBox(_("Restore old values?"), + _("Removing cheats"), + wxYES_NO|wxICON_QUESTION) == wxYES; + *dirty = true; + cheatsDeleteAll(restore); + } + } + Reload(); + break; + case wxID_SELECTALL: + // FIXME: probably ought to limit to selected items if any items + // are selected + *dirty = true; + if(isgb) { + int i; + for(i = 0; i < gbCheatNumber; i++) + if(!gbCheatList[i].enabled) + break; + if(i < gbCheatNumber) + for(; i < gbCheatNumber; i++) { + gbCheatEnable(i); + list->Check(i, true); + } + else + for(i = 0; i < gbCheatNumber; i++) { + gbCheatDisable(i); + list->Check(i, false); + } + } else { + int i; + for(i = 0; i < cheatsNumber; i++) + if(!cheatsList[i].enabled) + break; + if(i < cheatsNumber) + for(; i < cheatsNumber; i++) { + cheatsEnable(i); + list->Check(i, true); + } + else + for(i = 0; i < cheatsNumber; i++) { + cheatsDisable(i); + list->Check(i, false); + } + } + break; + } + } + + void Check(wxListEvent &ev) + { + int ch = ev.GetIndex(); + if(isgb) { + if(!gbCheatList[ch].enabled) { + gbCheatEnable(ev.GetIndex()); + *dirty = true; + } + } else { + if(!cheatsList[ch].enabled) { + cheatsEnable(ev.GetIndex()); + *dirty = true; + } + } + } + + void UnCheck(wxListEvent &ev) + { + int ch = ev.GetIndex(); + if(isgb) { + if(gbCheatList[ch].enabled) { + gbCheatDisable(ev.GetIndex()); + *dirty = true; + } + } else { + if(cheatsList[ch].enabled) { + cheatsDisable(ev.GetIndex()); + *dirty = true; + } + } + } + + void AddCheat() + { + wxStringTokenizer tk(ce_codes.MakeUpper()); + while(tk.HasMoreTokens()) { + wxString tok = tk.GetNextToken(); + if(isgb) { + if(!ce_type) + gbAddGsCheat(tok.mb_str(), ce_desc.mb_str()); + else + gbAddGgCheat(tok.mb_str(), ce_desc.mb_str()); + } else { + if(!ce_type) + cheatsAddCheatCode(tok.mb_str(), ce_desc.mb_str()); + // following determination of type by lengths is + // same used by win32 and gtk code + // and like win32/gtk code, user-chosen fmt is ignored + else if(tok.size() == 12) { + tok = tok.substr(0, 8) + wxT(' ') + tok.substr(8); + cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str()); + } else if(tok.size() == 16) + // not sure why 1-tok is !v3 and 2-tok is v3.. + cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), false); + // CBA codes are assumed to be N+4, and anything else + // is assumed to be GSA v3 (although I assume the + // actual formats should be 8+4 and 8+8) + else { + if(!tk.HasMoreTokens()) { + // throw an error appropriate to chosen type + if(ce_type == 1) // GSA + cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), false); + else + cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str()); + } else { + wxString tok2 = tk.GetNextToken(); + if(tok2.size() == 4) { + tok += wxT(' ') + tok2; + cheatsAddCBACode(tok.mb_str(), ce_desc.mb_str()); + } else { + tok += tok2; + cheatsAddGSACode(tok.mb_str(), ce_desc.mb_str(), true); + } + } + } + } + } + } + + void Edit(wxListEvent &ev) + { + int id = ev.GetIndex(); + + // GetItem() followed by GetText doesn't work, so retrieve from + // source + wxString odesc, ocode; + bool ochecked; + int otype; + if(isgb) { + ochecked = gbCheatList[id].enabled; + ce_codes = ocode = wxString(gbCheatList[id].cheatCode, wxConvLibc); + ce_desc = odesc = wxString(gbCheatList[id].cheatDesc, wxConvUTF8); + if(ce_codes.find(wxT('-')) == wxString::npos) + otype = ce_type = 0; + else + otype = ce_type = 1; + } else { + ochecked = cheatsList[id].enabled; + ce_codes = ocode = wxString(cheatsList[id].codestring, wxConvLibc); + ce_desc = odesc = wxString(cheatsList[id].desc, wxConvUTF8); + if(ce_codes.find(wxT(':')) != wxString::npos) + otype = ce_type = 0; + else if(ce_codes.find(wxT(' ')) == wxString::npos) + otype = ce_type = 1; + else + otype = ce_type = 2; + } + wxDialog *subdlg = GetXRCDialog("CheatEdit"); + if(subdlg->ShowModal() != wxID_OK) + return; + if(otype != ce_type || ocode != ce_codes) { + // vba core certainly doesn't make this easy + // there is no "change" function, so the only way to retain + // the old order is to delete this and all subsequent items, and + // then re-add them + // The MFC code got around this by not even supporting edits on + // gba codes (which have order dependencies) and just forcing + // edited codes to the rear on gb codes. + // It might be safest to only support desc edits, and force the + // user to re-enter codes to change them + int ncodes = isgb ? gbCheatNumber : cheatsNumber; + if(ncodes > id + 1) { + wxString codes[ncodes - id - 1]; + wxString descs[ncodes - id - 1]; + bool checked[ncodes - id - 1]; + bool v3[ncodes - id - 1]; + for(int i = id + 1; i < ncodes; i++) { + codes[i - id - 1] = wxString(isgb ? + gbCheatList[i].cheatCode : + cheatsList[i].codestring, + wxConvLibc); + descs[i - id - 1] = wxString(isgb ? + gbCheatList[i].cheatDesc : + cheatsList[i].desc, + wxConvUTF8); + checked[i - id - 1] = isgb ? gbCheatList[i].enabled : + cheatsList[i].enabled; + v3[i - id - 1] = isgb ? false : cheatsList[i].code == 257; + } + for(int i = ncodes - 1; i >= id; i--) { + list->DeleteItem(i); + if(isgb) + gbCheatRemove(i); + else + cheatsDelete(i, cheatsList[i].enabled); + } + AddCheat(); + if(!ochecked) { + if(isgb) + gbCheatDisable(id); + else + cheatsDisable(id); + } + for(int i = id + 1; i < ncodes; i++) { + ce_codes = codes[i - id - 1]; + ce_desc = descs[i - id - 1]; + if(isgb) { + if(ce_codes.find(wxT('-')) == wxString::npos) + ce_type = 0; + else + ce_type = 1; + } else { + if(ce_codes.find(wxT(':')) != wxString::npos) + ce_type = 0; + else if(ce_codes.find(wxT(' ')) == wxString::npos) { + ce_type = 1; + if(v3[i - id - 1]) + ce_codes.insert(8, 1, wxT(' ')); + } else + ce_type = 2; + } + AddCheat(); + if(!checked[i - id - 1]) { + if(isgb) + gbCheatDisable(i); + else + cheatsDisable(i); + } + } + } else { + list->DeleteItem(id); + if(isgb) + gbCheatRemove(id); + else + cheatsDelete(id, cheatsList[id].enabled); + AddCheat(); + if(!ochecked) { + if(isgb) + gbCheatDisable(id); + else + cheatsDisable(id); + } + } + Reload(id); + } else if(ce_desc != odesc) { + *dirty = true; + char *p = isgb ? gbCheatList[id].cheatDesc : cheatsList[id].desc; + strncpy(p, ce_desc.mb_str(), sizeof(cheatsList[0].desc)); + p[sizeof(cheatsList[0].desc) - 1] = 0; + item1.SetId(id); + item1.SetText(wxString(p, wxConvUTF8)); + list->SetItem(item1); + } + } + + void AdjustDescWidth() + { + // why is it so hard to get an accurate measurement out of wx? + // on msw, wxLIST_AUTOSIZE might actually be accurate. On wxGTK, + // and probably wxMAC (both of which use generic impl) wrong + // font is used both for rendering (col 0's font) and for + // wxLIST_AUTOSIZE calculation (the widget's font). + // The only way to defeat this is to calculate size manually + // Instead, this just allows user to set max size, and retains + // it. + int ow = list->GetColumnWidth(1); + list->SetColumnWidth(1, wxLIST_AUTOSIZE); + int cw = list->GetColumnWidth(1); + // subtracted in renderer from width avail for text + // but not added in wxLIST_AUTOSIZE + cw += 8; + if(cw < col1minw) + cw = col1minw; + if(cw < ow) + cw = ow; + list->SetColumnWidth(1, cw); + } +} cheat_list_handler; + +// onshow handler for above, in the form of an overzealous validator +class CheatListFill : public wxValidator +{ +public: + CheatListFill() : wxValidator() {} + CheatListFill(const CheatListFill &e) : wxValidator() {} + wxObject *Clone() const { return new CheatListFill(*this); } + bool TransferFromWindow() { return true; } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() { + CheatList_t &clh = cheat_list_handler; + GameArea *panel = wxGetApp().frame->GetPanel(); + clh.isgb = panel->game_type() == IMAGE_GB; + clh.dirty = &panel->cheats_dirty; + clh.cheatfn = panel->game_name() + wxT(".clt"); + clh.cheatdir = panel->game_dir(); + clh.deffn = wxFileName(clh.cheatdir, clh.cheatfn).GetFullPath(); + clh.Reload(); + + clh.ce_desc = wxEmptyString; + wxChoice *ch = clh.ce_type_ch; + ch->Clear(); + if(clh.isgb) { + ch->Append(_("GameShark")); + ch->Append(_("GameGenie")); + } else { + ch->Append(_("Generic Code")); + ch->Append(_("GameShark Advance")); + ch->Append(_("CodeBreaker Advance")); + } + ch->SetSelection(0); + + return true; + } +}; + +// manage the cheat search dialog +enum cf_vfmt { + CFVFMT_SD, CFVFMT_UD, CFVFMT_UH +}; + +// virtual ListCtrl for cheat search results +class CheatListCtrl : public wxListCtrl +{ + public: + wxArrayInt addrs; + int cap_size; // size in effect when addrs were generated + int count8, count16, count32; // number of aligned addresses in addrs + wxString OnGetItemText(long item, long column) const; + + DECLARE_DYNAMIC_CLASS() +}; + +IMPLEMENT_DYNAMIC_CLASS(CheatListCtrl, wxListCtrl); + +static class CheatFind_t : public wxEvtHandler +{ +public: + wxDialog *dlg; + int valsrc, size, op, fmt; + int ofmt, osize; + wxString val_s; + wxTextCtrl *val_tc; + CheatListCtrl *list; + + // for enable/disable + wxRadioButton *old_rb, *val_rb; + wxControl *update_b, *clear_b, *add_b; + + bool isgb; + + // add dialog + wxString ca_desc, ca_val; + wxTextCtrl *ca_val_tc; + wxControl *ca_fmt, *ca_addr; + + CheatFind_t() : wxEvtHandler(), valsrc(0), size(0), op(0), fmt(0), val_s() {} + ~CheatFind_t() + { + // not that it matters to anyone but mem leak detectors.. + cheatSearchCleanup(&cheatSearchData); + } + + void Search(wxCommandEvent &ev) + { + dlg->TransferDataFromWindow(); + if(!valsrc && val_s.empty()) { + wxLogError(_("Number cannot be empty")); + return; + } + if(!cheatSearchData.count) + ResetSearch(ev); + if(valsrc) + cheatSearch(&cheatSearchData, op, size, fmt == CFVFMT_SD); + else + cheatSearchValue(&cheatSearchData, op, size, fmt == CFVFMT_SD, + SignedValue()); + Deselect(); + list->addrs.clear(); + list->count8 = list->count16 = list->count32 = 0; + list->cap_size = size; + for(int i = 0; i < cheatSearchData.count; i++) { + CheatSearchBlock *block = &cheatSearchData.blocks[i]; + for(int j = 0; j < block->size; j += (1 << size)) { + if(IS_BIT_SET(block->bits, j)) { + list->addrs.push_back((i << 28) + j); + if(!(j & 1)) + list->count16++; + if(!(j & 3)) + list->count32++; + // since listctrl is virtual, it should be able to handle + // at least 256k results, which is about the most you + // will ever get +#if 0 + if(list->addrs.size() > 1000) { + wxLogError(_("Search produced %d results. Please refine better"), + list->addrs.size()); + list->addrs.clear(); + return; + } +#endif + } + } + } + if(list->addrs.empty()) { + wxLogError(_("Search produced no results")); + // no point in keeping empty search results around + ResetSearch(ev); + if(old_rb->GetValue()) { + val_rb->SetValue(true); + // SetValue doesn't generate an event + val_tc->Enable(); + } + old_rb->Disable(); + update_b->Disable(); + clear_b->Disable(); + } else { + switch(size) { + case BITS_32: + list->count16 = list->count32 * 2; + // fall through + case BITS_16: + list->count8 = list->count16 * 2; + break; + case BITS_8: + list->count8 = list->addrs.size(); + } + old_rb->Enable(); + update_b->Enable(); + clear_b->Enable(); + } + list->SetItemCount(list->addrs.size()); + list->Refresh(); + } + + void UpdateVals(wxCommandEvent &ev) + { + if(cheatSearchData.count) { + cheatSearchUpdateValues(&cheatSearchData); + if(list->count8) + list->Refresh(); + update_b->Disable(); + } + } + + void ResetSearch(wxCommandEvent &ev) + { + if(!cheatSearchData.count) { + CheatSearchBlock *block = cheatSearchData.blocks; + + if(isgb) { + block->offset = 0xa000; + if(gbRam) + block->data = gbRam; + else + block->data = &gbMemory[0xa000]; + block->saved = (u8 *)malloc(gbRamSize); + block->size = gbRamSize; + block->bits = (u8 *)malloc(gbRamSize >> 3); + if(gbCgbMode) { + block++; + block->offset = 0xc000; + block->data = &gbMemory[0xc000]; + block->saved = (u8 *)malloc(0x1000); + block->size = 0x1000; + block->bits = (u8 *)malloc(0x1000 >> 3); + block++; + block->offset = 0xd000; + block->data = gbWram; + block->saved = (u8 *)malloc(0x8000); + block->size = 0x8000; + block->bits = (u8 *)malloc(0x8000 >> 3); + } else { + block++; + block->offset = 0xc000; + block->data = &gbMemory[0xc000]; + block->saved = (u8 *)malloc(0x2000); + block->size = 0x2000; + block->bits = (u8 *)malloc(0x2000 >> 3); + } + } else { + block->size = 0x40000; + block->offset = 0x2000000; + block->bits = (u8 *)malloc(0x40000 >> 3); + block->data = workRAM; + block->saved = (u8 *)malloc(0x40000); + block++; + block->size = 0x8000; + block->offset = 0x3000000; + block->bits = (u8 *)malloc(0x8000 >> 3); + block->data = internalRAM; + block->saved = (u8 *)malloc(0x8000); + } + cheatSearchData.count = (int)((block + 1) - cheatSearchData.blocks); + } + cheatSearchStart(&cheatSearchData); + if(list->count8) { + Deselect(); + list->count8 = list->count16 = list->count32 = 0; + list->addrs.clear(); + list->SetItemCount(0); + list->Refresh(); + if(old_rb->GetValue()) { + val_rb->SetValue(true); + // SetValue doesn't generate an event + val_tc->Enable(); + } + old_rb->Disable(); + update_b->Disable(); + clear_b->Disable(); + } + } + + void Deselect() + { + int idx = list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if(idx >= 0) + list->SetItemState(idx, 0, wxLIST_STATE_SELECTED); + add_b->Disable(); + } + + void Select(wxListEvent &ev) + { + add_b->Enable(list->GetItemState(ev.GetIndex(), wxLIST_STATE_SELECTED) != 0); + } + + void AddCheatB(wxCommandEvent &ev) + { + int idx = list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if(idx >= 0) + AddCheat(idx); + } + + void AddCheatL(wxListEvent &ev) + { + AddCheat(ev.GetIndex()); + } + + void AddCheat(int idx) + { + wxString addr_s = list->OnGetItemText(idx, 0); + ca_addr->SetLabel(addr_s); + wxString s; + switch(size) { + case BITS_8: + s = _("8-bit "); + break; + case BITS_16: + s = _("16-bit "); + break; + case BITS_32: + s = _("32-bit "); + break; + } + switch(fmt) { + case CFVFMT_SD: + s += _("signed decimal"); + break; + case CFVFMT_UD: + s += _("unsigned decimal"); + break; + case CFVFMT_UH: + s += _("unsigned hexadecimal"); + break; + } + ca_fmt->SetLabel(s); + // probably pointless (but inoffensive) to suggest a value + ca_val = list->OnGetItemText(idx, 2); // sugest "New" value + SetValVal(ca_val_tc); + wxDialog *subdlg = GetXRCDialog("CheatAdd"); + if(subdlg->ShowModal() != wxID_OK) + return; + if(ca_val.empty()) { + wxLogError(_("Number cannot be empty")); + return; + } + u32 val = GetValue(ca_val, fmt); + if(isgb) { + long bank, addr; + addr_s.ToLong(&bank, 16); + addr_s.erase(0, 3); + addr_s.ToLong(&addr, 16); + if(addr >= 0xd000) + bank += 0x90; + else + bank = 1; + for(int i = 0; i < (1 << size); i++) { + addr_s.Printf(wxT("%02X%02X%02X%02X"), bank, val & 0xff, + addr & 0xff, addr >> 8); + gbAddGsCheat(addr_s.mb_str(), ca_desc.mb_str()); + val >>= 8; + addr++; + } + } else { + wxString s; + switch(size) { + case BITS_8: + s.Printf(wxT(":%02X"), val); + break; + case BITS_16: + s.Printf(wxT(":%04X"), val); + break; + case BITS_32: + s.Printf(wxT(":%08X"), val); + break; + } + addr_s.append(s); + cheatsAddCheatCode(addr_s.mb_str(), ca_desc.mb_str()); + } + } + + void SetValVal(wxTextCtrl *tc) + { + wxTextValidator *v = wxStaticCast(tc->GetValidator(), wxTextValidator); + switch(fmt) { + case CFVFMT_SD: + v->SetIncludes(val_sigdigits); + break; + case CFVFMT_UD: + v->SetIncludes(val_unsdigits); + break; + case CFVFMT_UH: + v->SetIncludes(val_hexdigits); + break; + } + } + + u32 GetValue(wxString &s, int fmt) + { + long val; + // FIXME: probably ought to throw an error if ToLong + // returns false or val is out of range + s.ToLong(&val, fmt == CFVFMT_UH ? 16 : 10); + if(size != BITS_32) + val &= size == BITS_8 ? 0xff : 0xffff; + return val; + } + + u32 GetValue(int fmt) + { + return GetValue(val_s, fmt); + } + + u32 GetValue() + { + return GetValue(fmt); + } + + s32 SignedValue(wxString &s, int fmt) + { + s32 val = GetValue(s, fmt); + if(fmt == CFVFMT_SD) { + if(size == BITS_8) + val = (s32)(s8)val; + else if(size == BITS_16) + val = (s32)(s16)val; + } + return val; + } + + s32 SignedValue(int fmt) + { + return SignedValue(val_s, fmt); + } + + s32 SignedValue() + { + return SignedValue(fmt); + } + + void FormatValue(s32 val, wxString &s) + { + if(fmt != CFVFMT_SD && size != BITS_32) + val &= size == BITS_8 ? 0xff : 0xffff; + switch(fmt) { + case CFVFMT_SD: + s.Printf(wxT("%d"), val); + break; + case CFVFMT_UD: + s.Printf(wxT("%u"), val); + break; + case CFVFMT_UH: + switch(size) { + case BITS_8: + s.Printf(wxT("%02X"), val); + break; + case BITS_16: + s.Printf(wxT("%04X"), val); + break; + case BITS_32: + s.Printf(wxT("%08X"), val); + break; + } + } + } + + void UpdateView(wxCommandEvent &ev) + { + dlg->TransferDataFromWindow(); + if(ofmt != fmt && !val_s.empty()) { + s32 val = GetValue(ofmt); + switch(fmt) { + case CFVFMT_SD: + switch(size) { + case BITS_8: + val = (s32)(s8)val; + break; + case BITS_16: + val = (s32)(s16)val; + } + val_s.Printf(wxT("%d"), val); + break; + case CFVFMT_UD: + val_s.Printf(wxT("%u"), val); + break; + case CFVFMT_UH: + val_s.Printf(wxT("%x"), val); + break; + } + val_tc->SetValue(val_s); + } + if(ofmt != fmt) + SetValVal(val_tc); + if(list->count8 && osize != size) { + switch(size) { + case BITS_32: + list->SetItemCount(list->count32); + break; + case BITS_16: + list->SetItemCount(list->count16); + break; + case BITS_8: + list->SetItemCount(list->count8); + break; + } + } + if(ofmt != fmt || osize != size) + list->Refresh(); + ofmt = fmt; + osize = size; + } + + void EnableVal(wxCommandEvent &ev) + { + val_tc->Enable(ev.GetId() == XRCID("SpecificValue")); + } + +} cheat_find_handler; + +// clear cheat find dialog between games +void MainFrame::ResetCheatSearch() +{ + CheatFind_t &cfh = cheat_find_handler; + cfh.fmt = cfh.size = cfh.op = cfh.valsrc = 0; + cfh.val_s = wxEmptyString; + cfh.Deselect(); + cfh.list->SetItemCount(0); + cfh.list->count8 = cfh.list->count16 = cfh.list->count32 = 0; + cfh.list->addrs.clear(); + cfh.ca_desc = wxEmptyString; + cheatSearchCleanup(&cheatSearchData); +} + +// onshow handler for above, in the form of an overzealous validator +class CheatFindFill : public wxValidator +{ +public: + CheatFindFill() : wxValidator() {} + CheatFindFill(const CheatFindFill &e) : wxValidator() {} + wxObject *Clone() const { return new CheatFindFill(*this); } + bool TransferFromWindow() { return true; } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() { + CheatFind_t &cfh = cheat_find_handler; + GameArea *panel = wxGetApp().frame->GetPanel(); + cfh.isgb = panel->game_type() == IMAGE_GB; + cfh.val_tc->Enable(!cfh.valsrc); + cfh.ofmt = cfh.fmt; + cfh.SetValVal(cfh.val_tc); + return true; + } +}; + +// the implementation of the virtual list ctrl for search results +// requires CheatFind_t to be implemented +wxString CheatListCtrl::OnGetItemText(long item, long column) const +{ + wxString s; + CheatFind_t &cfh = cheat_find_handler; + // allowing GUI to change format after search makes this a little + // more complicated than necessary... + int off = 0; + int size = cfh.size; + if(cap_size > size) { + off = (item & ((1 << (cap_size - size)) - 1)) << size; + item >>= cap_size - size; + } else if(cap_size < size) { + for(int i = 0; i < addrs.size(); i++) { + if(!(addrs[i] & ((1 << size) - 1)) && !item--) { + item = i; + break; + } + } + } + CheatSearchBlock *block = &cheatSearchData.blocks[addrs[item] >> 28]; + off += addrs[item] & 0xfffffff; + + switch(column) { + case 0: // address + if(cfh.isgb) { + int bank = 0; + int addr = block->offset; + if(block->offset == 0xa000) { + bank = off / 0x2000; + addr += off % 0x2000; + } else if(block->offset == 0xd000) { + bank = off / 0x1000; + addr += off % 0x1000; + } else + addr += off; + s.Printf(wxT("%02X:%04X"), bank, addr); + } else + s.Printf(wxT("%08X"), block->offset + off); + break; + case 1: // old + cfh.FormatValue(cheatSearchSignedRead(block->saved, off, size), s); + break; + case 2: // new + cfh.FormatValue(cheatSearchSignedRead(block->data, off, size), s); + break; + } + return s; +} + +// these are the choices for canned colors; their order must match the +// names in the choice control +static const u16 defaultPalettes[][8] = { + { // Standard + 0x7FFF, 0x56B5, 0x318C, 0x0000, 0x7FFF, 0x56B5, 0x318C, 0x0000, + }, + { // Blue Sea + 0x6200, 0x7E10, 0x7C10, 0x5000, 0x6200, 0x7E10, 0x7C10, 0x5000, + }, + { // Dark Night + 0x4008, 0x4000, 0x2000, 0x2008, 0x4008, 0x4000, 0x2000, 0x2008, + }, + { // Green Forest + 0x43F0, 0x03E0, 0x4200, 0x2200, 0x43F0, 0x03E0, 0x4200, 0x2200, + }, + { // Hot Desert + 0x43FF, 0x03FF, 0x221F, 0x021F, 0x43FF, 0x03FF, 0x221F, 0x021F, + }, + { // Pink Dreams + 0x621F, 0x7E1F, 0x7C1F, 0x2010, 0x621F, 0x7E1F, 0x7C1F, 0x2010, + }, + { // Weird Colors + 0x621F, 0x401F, 0x001F, 0x2010, 0x621F, 0x401F, 0x001F, 0x2010, + }, + { // Real GB Colors + 0x1B8E, 0x02C0, 0x0DA0, 0x1140, 0x1B8E, 0x02C0, 0x0DA0, 0x1140, + }, + { // Real 'GB on GBASP' Colors + 0x7BDE, /*0x23F0*/ 0x5778, /*0x5DC0*/ 0x5640, 0x0000, 0x7BDE, /*0x3678*/ 0x529C, /*0x0980*/ 0x2990, 0x0000, + } +}; + +// manage the GB color prefs' canned color selecter +static class GBColorConfig_t : public wxEvtHandler +{ +public: + wxWindow *p; + wxChoice *c; + wxColourPickerCtrl *cp[8]; + int pno; + void ColorSel(wxCommandEvent &ev) + { + if(ev.GetSelection() > 0) { + const u16 *color = defaultPalettes[ev.GetSelection() - 1]; + for(int i = 0; i < 8; i++, color++) + cp[i]->SetColour(wxColor(((*color << 3) & 0xf8), + ((*color >> 2) & 0xf8), + ((*color >> 7) & 0xf8))); + } + } + void ColorReset(wxCommandEvent &ev) + { + const u16 *color = &systemGbPalette[pno * 8]; + for(int i = 0; i < 8; i++, color++) + cp[i]->SetColour(wxColor(((*color << 3) & 0xf8), + ((*color >> 2) & 0xf8), + ((*color >> 7) & 0xf8))); + } + + void ColorButton(wxCommandEvent &ev) + { + c->SetSelection(0); + } +} GBColorConfigHandler[3]; + +// disable controls if a GBA game is not loaded +class GBACtrlEnabler : public wxValidator +{ +public: + GBACtrlEnabler() : wxValidator() {} + GBACtrlEnabler(const GBACtrlEnabler &e) : wxValidator() {} + wxObject *Clone() const { return new GBACtrlEnabler(*this); } + bool TransferFromWindow() { return true; } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() { + GetWindow()->Enable(wxGetApp().frame->GetPanel()->game_type() == IMAGE_GBA); + return true; + } +}; + +// manage save game area settings for GBA prefs +static class BatConfig_t : public wxEvtHandler +{ +public: + wxChoice *type, *size; + void ChangeType(wxCommandEvent &ev) + { + int i = ev.GetSelection(); + size->Enable(!i || i == 3); // automatic/flash + } + void Detect(wxCommandEvent &ev) + { + // note: win32 version just pops up a dialog stating what it found + // which is appropriate becauase it was in a menu + // this code sets the controls instead, since there right there + u32 sz = wxGetApp().frame->GetPanel()->game_size(); +#define ch4(a, b, c, d) \ + wxUINT32_SWAP_ON_BE(a + (b << 8) + (c << 16) + (d << 24)) + for(u32 addr = 0; addr < sz - 10; addr += 4) { + switch(*(u32 *)&rom[addr]) { + case ch4('E', 'E', 'P', 'R'): + if(memcmp(&rom[addr + 4], "OM_V", 4)) + break; + // apparently no sensor autodetection + type->SetSelection(1); + size->Disable(); + return; + case ch4('S', 'R', 'A', 'M'): + if(memcmp(&rom[addr + 4], "_V", 2)) + break; + type->SetSelection(2); + size->Disable(); + return; + case ch4('F', 'L', 'A', 'S'): + if(!memcmp(&rom[addr + 4], "H_V", 3)) { + type->SetSelection(3); + size->SetSelection(0); + size->Enable(); + return; + } else if(!memcmp(&rom[addr + 4], "H1M_V", 5)) { + type->SetSelection(3); + size->SetSelection(1); + size->Enable(); + return; + } + break; + } + } + type->SetSelection(5); + size->Disable(); + } +} BatConfigHandler; + +// manage the sound prefs dialog +static class SoundConfig_t : public wxEvtHandler +{ +public: + wxSlider *vol, *bufs; + wxControl *bufinfo; + int lastapi; + wxChoice *dev; + wxControl *umix, *hwacc; + wxArrayString dev_ids; + + void FullVol(wxCommandEvent &ev) + { + vol->SetValue(100); + } + void AdjustFrames(int count) + { + wxString s; + s.Printf(_("%d frames = %.2f ms"), count, (double)count / 60.0 * 1000.0); + bufinfo->SetLabel(s); + } + void AdjustFramesEv(wxCommandEvent &ev) + { + AdjustFrames(bufs->GetValue()); + } + + bool FillDev(int api) + { + dev->Clear(); + dev->Append(_("Default device")); + dev_ids.clear(); + wxArrayString names; + switch(api) { + case AUD_SDL: + break; +#ifndef NO_OAL + case AUD_OPENAL: + if(!GetOALDevices(names, dev_ids)) + return false; + break; +#endif +#ifdef __WXMSW__ + case AUD_DIRECTSOUND: + if(!(GetDSDevices(names, dev_ids))) + return false; + break; +#ifndef NO_XAUDIO2 + case AUD_XAUDIO2: + if(!GetXA2Devices(names, dev_ids)) + return false; + break; +#endif +#endif + } + dev->SetSelection(0); + for(int i = 0; i < names.size(); i++) { + dev->Append(names[i]); + if(api == gopts.audio_api && gopts.audio_dev == dev_ids[i]) + dev->SetSelection(i + 1); + } + umix->Enable(api == AUD_XAUDIO2); + hwacc->Enable(api == AUD_DIRECTSOUND); + lastapi = api; + } + void SetAPI(wxCommandEvent &ev) + { + int api = gopts.audio_api; + wxValidator *v = wxStaticCast(ev.GetEventObject(), wxWindow)->GetValidator(); + v->TransferFromWindow(); + int newapi = gopts.audio_api; + gopts.audio_api = api; + if(newapi == lastapi) + return; + FillDev(newapi); + } +} sound_config_handler; + +// Validator/widget filler for sound device selector & time indicator +class SoundConfigLoad : public wxValidator +{ +public: + SoundConfigLoad() : wxValidator() {} + SoundConfigLoad(const SoundConfigLoad &e) : wxValidator() {} + wxObject *Clone() const { return new SoundConfigLoad(*this); } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() { + SoundConfig_t &sch = sound_config_handler; + sch.FillDev(gopts.audio_api); + sch.AdjustFrames(gopts.audio_buffers); + return true; + } + bool TransferFromWindow() { + SoundConfig_t &sch = sound_config_handler; + int devs = sch.dev->GetSelection(); + if(!devs) + gopts.audio_dev = wxEmptyString; + else + gopts.audio_dev = sch.dev_ids[devs - 1]; + return true; + } +}; + +// manage the joypad prefs' per-panel default/clear buttons +static class JoyPadConfig_t : public wxEvtHandler +{ +public: + wxWindow *p; + void JoypadConfigButtons(wxCommandEvent &ev) { + bool clear = ev.GetId() == XRCID("Clear"); + for(int i = 0; i < NUM_KEYS; i++) { + wxJoyKeyTextCtrl *tc = XRCCTRL_D(*p, joynames[i], wxJoyKeyTextCtrl); + if(clear) + tc->SetValue(wxEmptyString); + else { + wxJoyKeyBinding_v a; + if(defkeys[i*2].key) + a.push_back(defkeys[i*2]); + if(defkeys[i*2+1].joy) + a.push_back(defkeys[i*2+1]); + tc->SetValue(wxJoyKeyTextCtrl::ToString(a)); + } + } + + } +} JoyPadConfigHandler[4]; + +#ifndef NO_LINK +// tc validator for IP addresses using SFML for validation instead of wx +class IPHostValidator : public wxValidator +{ + wxString *valp; +public: + IPHostValidator(wxString *v) : wxValidator(), valp(v) {} + IPHostValidator(const IPHostValidator &e) : wxValidator(), valp(e.valp) {} + wxObject *Clone() const { return new IPHostValidator(*this); } + bool Validate(wxWindow *p) { + wxTextCtrl *tc = wxStaticCast(GetWindow(), wxTextCtrl); + if(!tc->IsEnabled()) + return true; + wxString val = tc->GetValue(); + bool isv = true; + if(val.empty()) + isv = false; + else { + sf::IPAddress srv = std::string(val.mb_str()); + isv = srv.IsValid(); + } + if(!isv) + wxMessageBox(_("You must enter a valid host name"), + _("Host name invalid"), wxICON_ERROR|wxOK); + return isv; + } + bool TransferToWindow() { + wxTextCtrl *tc = wxStaticCast(GetWindow(), wxTextCtrl); + tc->SetValue(*valp); + return true; + } + bool TransferFromWindow() { + wxTextCtrl *tc = wxStaticCast(GetWindow(), wxTextCtrl); + *valp = tc->GetValue(); + return true; + } +}; +#endif + +// manage fullscreen mode widget +// technically, it's more than a validator: it modifies the widget as well +class ScreenModeList : public wxValidator +{ +public: + ScreenModeList() : wxValidator() {} + ScreenModeList(const ScreenModeList &e) : wxValidator() {} + wxObject *Clone() const { return new ScreenModeList(*this); } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() + { + wxChoice *c = wxStaticCast(GetWindow(), wxChoice); + wxDisplay d(wxDisplay::GetFromWindow(c->GetParent())); + c->Clear(); + int modeno = 0, bestmode = 0; + int bm_bpp = 0; + c->Append(_("Desktop mode")); + // probably ought to just disable this whole control on UNIX/X11 since + // wxDisplay is so broken. + vm = d.GetModes(); + wxString s; + for(int i = 0; i < vm.size(); i++) { + s.Printf(_("%d x %d - %dbpp @ %dHz"), vm[i].w, vm[i].h, vm[i].bpp, vm[i].refresh); + c->Append(s); + if(!modeno && gopts.fs_mode.w == vm[i].w && gopts.fs_mode.h == vm[i].h) { + if(gopts.fs_mode.bpp == vm[i].bpp && gopts.fs_mode.refresh == vm[i].refresh) + modeno = i + 1; + else if(vm[i].bpp == gopts.fs_mode.bpp && + bm_bpp != gopts.fs_mode.bpp) { + bestmode = i + 1; + bm_bpp = vm[i].bpp; + } else if(bm_bpp != gopts.fs_mode.bpp && bm_bpp != 32 && + vm[i].bpp == 32) { + bm_bpp = vm[i].bpp; + bestmode = i + 1; + } else if(bm_bpp != gopts.fs_mode.bpp && bm_bpp < 24 && + vm[i].bpp == 24) { + bm_bpp = vm[i].bpp; + bestmode = i + 1; + } else if(bm_bpp != gopts.fs_mode.bpp && bm_bpp < 24 && + bm_bpp != 16 && vm[i].bpp == 16) { + bm_bpp = vm[i].bpp; + bestmode = i + 1; + } else if(!bm_bpp) { + bm_bpp = vm[i].bpp; + bestmode = i + 1; + } + } + } + if(!modeno && bestmode) + modeno = bestmode; + c->SetSelection(modeno); + return true; + } + bool TransferFromWindow() + { + int bestmode = wxStaticCast(GetWindow(), wxChoice)->GetSelection(); + if(!bestmode) + gopts.fs_mode.h = gopts.fs_mode.w = gopts.fs_mode.bpp = gopts.fs_mode.refresh = 0; + else + gopts.fs_mode = vm[bestmode - 1]; + return true; + } +private: + wxArrayVideoModes vm; +}; + +// enable plugin-related iff filter choice is plugin +class PluginEnabler : public wxValidator +{ +public: + PluginEnabler() : wxValidator() {} + PluginEnabler(const PluginEnabler &e) : wxValidator() {} + wxObject *Clone() const { return new PluginEnabler(*this); } + bool TransferFromWindow() { return true; } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() + { + GetWindow()->Enable(gopts.filter == FF_PLUGIN); + return true; + } +}; + +// The same, but as an event handler +static class PluginEnable_t : public wxEvtHandler +{ +public: + wxWindow *lab, *ch; + void ToggleChoice(wxCommandEvent &ev) + { + bool en = ev.GetSelection() == FF_PLUGIN; + lab->Enable(en); + ch->Enable(en); + } +} PluginEnableHandler; + +// fill in plugin list +class PluginListFiller : public PluginEnabler +{ +public: + PluginListFiller(wxDialog *parent, wxControl *lab, wxChoice *ch) : + PluginEnabler(), txt(lab), dlg(parent), plugins(), filtch(ch) {} + PluginListFiller(const PluginListFiller &e) : + PluginEnabler(), txt(e.txt), dlg(e.dlg), plugins(e.plugins), + filtch(e.filtch) {} + wxObject *Clone() const { return new PluginListFiller(*this); } + bool Validate(wxWindow *p) { return true; } + bool TransferToWindow() + { + PluginEnabler::TransferToWindow(); + wxChoice *ch = wxStaticCast(GetWindow(), wxChoice); + ch->Clear(); + ch->Append(_("None")); + plugins.clear(); + const wxString &plpath = wxStandardPaths::Get().GetPluginsDir(); + wxDir::GetAllFiles(plpath, &plugins, wxT("*.rpi")); + for(int i = 0; i < plugins.size(); i++) { + wxDynamicLibrary dl(plugins[i], wxDL_VERBATIM|wxDL_NOW); + RENDPLUG_GetInfo GetInfo; + const RENDER_PLUGIN_INFO *rpi; + if(dl.IsLoaded() && + (GetInfo = (RENDPLUG_GetInfo)dl.GetSymbol(wxT("RenderPluginGetInfo"))) && + // note that in actual kega fusion plugins, rpi->Output is + // unused (as is rpi->Handle) + dl.GetSymbol(wxT("RenderPluginOutput")) && + (rpi = GetInfo()) && + // FIXME: maybe this should be >= RPI_VERISON + (rpi->Flags & 0xff) == RPI_VERSION && + // RPI_565_SUPP is not supported + // although it would be possible + // and it would make Cairo more efficient + (rpi->Flags & (RPI_555_SUPP|RPI_888_SUPP))) { + wxFileName fn(plugins[i]); + wxString s = fn.GetName(); + s += wxT(": "); + s += wxString(rpi->Name, wxConvUTF8, sizeof(rpi->Name)); + fn.MakeRelativeTo(plpath); + plugins[i] = fn.GetFullName(); + ch->Append(s); + if(plugins[i] == gopts.filter_plugin) + ch->SetSelection(i + 1); + } else + plugins.RemoveAt(i--); + } + if(ch->GetCount() == 1) { + // this is probably the only place the user can find out where + // to put the plugins... it depends on where program was + // installed, and of course OS + wxString msg; + msg.Printf(_("No usable rpi plugins found in %s"), plpath.c_str()); + systemScreenMessage(msg); + ch->Hide(); + txt->Hide(); + int cursel = filtch->GetSelection(); + if(cursel == FF_PLUGIN) + cursel = 0; + if(filtch->GetCount() == FF_PLUGIN + 1) { + filtch->Delete(FF_PLUGIN); + // apparently wxgtk loses selection after this, even + // if selection was not FF_PLUGIN + filtch->SetSelection(cursel); + } + } else { + ch->Show(); + txt->Show(); + if(filtch->GetCount() < FF_PLUGIN + 1) + filtch->Append(_("Plugin")); + } + // FIXME: this isn't enough. It only resizes 2nd time around + dlg->Fit(); + return true; + } + bool TransferFromWindow() + { + wxChoice *ch = wxStaticCast(GetWindow(), wxChoice); + if(ch->GetCount() == 1) { + gopts.filter_plugin = wxEmptyString; + // this happens if "Plugin" was selected and the entry was + // subsequently removed + if(ch->GetSelection() < 0) + ch->SetSelection(0); + if(gopts.filter < 0) + gopts.filter = 0; + } else { + int n = ch->GetSelection(); + if(n > 0) + gopts.filter_plugin = plugins[n - 1]; + else { + if(filtch->GetSelection() == FF_PLUGIN) { + wxMessageBox(_("Please select a plugin or a different filter"), + _("Plugin selection error"), wxOK|wxICON_ERROR); + return false; + } + gopts.filter_plugin = wxEmptyString; + } + } + return true; + } +private: + wxDialog *dlg; + wxControl *txt; + wxChoice *filtch; + wxArrayString plugins; +}; + +// this is the cmd table index for the accel tree ctrl +// one of the "benefits" of using TreeItemData is that we have to +// malloc them all, because treectrl destructor will free them all +// that means we can't use e.g. a single static table of len ncmds +class TreeInt : public wxTreeItemData +{ +public: + TreeInt(int i) : wxTreeItemData() { val = i; } + int val; +}; + +// Convert a tree selection ID to a name +// root +// parent +// item +static bool treeid_to_name(int id, wxString &name, wxTreeCtrl *tc, + const wxTreeItemId &parent, int lev = 0) +{ + wxTreeItemIdValue cookie; + for(wxTreeItemId tid = tc->GetFirstChild(parent, cookie); tid.IsOk(); + tid = tc->GetNextChild(parent, cookie)) { + const TreeInt *ti = static_cast(tc->GetItemData(tid)); + if(ti && ti->val == id) { + name = wxString(wxT(' '), 2 * lev) + tc->GetItemText(tid); + return true; + } + if(treeid_to_name(id, name, tc, tid, lev + 1)) { + name = wxString(wxT(' '), 2 * lev) + tc->GetItemText(tid) + wxT('\n') + name; + return true; + } + } + return false; +} + +// for sorting accels by command ID +static bool cmdid_lt(const wxAcceleratorEntry &a, const wxAcceleratorEntry &b) +{ + return a.GetCommand() < b.GetCommand(); +} + +// manage the accel editor dialog +static class AccelConfig_t : public wxEvtHandler +{ +public: + wxTreeCtrl *tc; + wxControlWithItems *lb; + wxAcceleratorEntry_v user_accels, accels; + wxWindow *asb, *remb; + wxKeyTextCtrl *key; + wxControl *curas; + + // since this is not the actual dialog, derived from wxDialog, which is + // the normal way of doing things, do init on the show event instead of + // constructor + void Init(wxShowEvent &ev) + { +#if wxCHECK_VERSION(2,9,0) +#define GetShow IsShown +#endif + ev.Skip(); + if(!ev.GetShow()) + return; + lb->Clear(); + tc->Unselect(); + tc->ExpandAll(); + user_accels = gopts.accels; + key->SetValue(wxT("")); + asb->Enable(false); + remb->Enable(false); + curas->SetLabel(wxT("")); + accels = wxGetApp().frame->get_accels(user_accels); + } + + // on OK, save the accels in gopts + void Set(wxCommandEvent &ev) + { + // opts.cpp assumes that gopts.accels entries with same command ID + // are contiguous, so sort first + std::sort(gopts.accels.begin(), gopts.accels.end(), cmdid_lt); + gopts.accels = user_accels; + wxGetApp().frame->set_global_accels(); + ev.Skip(); + } + + // After selecting item in command list, fill in key list + // and maybe enable asb + void CommandSel(wxTreeEvent &ev) + { + // wxTreeCtrl *tc = wxStaticCast(evt.GetEventObject(), wxTreeCtrl); + // can't use wxStaticCast; wxTreeItemData does not derive from wxObject + const TreeInt *id = static_cast(tc->GetItemData(ev.GetItem())); + if(!id) { + ev.Veto(); + return; + } + if(ev.GetEventType() == wxEVT_COMMAND_TREE_SEL_CHANGING) { + ev.Skip(); + return; + } + lb->Clear(); + remb->Enable(false); + asb->Enable(!key->GetValue().empty()); + int cmd = id->val; + + for(int i = 0; i < accels.size(); i++) + if(accels[i].GetCommand() == cmdtab[cmd].cmd_id) + lb->Append(wxKeyTextCtrl::ToString(accels[i].GetFlags(), + accels[i].GetKeyCode())); + } + + // after selecting a key in key list, enable Remove button + void KeySel(wxCommandEvent &ev) + { + remb->Enable(lb->GetSelection() != wxNOT_FOUND); + } + + // remove selected binding + void Remove(wxCommandEvent &ev) + { + int lsel = lb->GetSelection(); + if(lsel == wxNOT_FOUND) + return; + wxString selstr = lb->GetString(lsel); + int selmod, selkey; + if(!wxKeyTextCtrl::FromString(selstr, selmod, selkey)) + return; // this should never happen + remb->Enable(false); + // if this key is currently in the shortcut field, clear out curas + if(selstr == key->GetValue()) + curas->SetLabel(wxT("")); + lb->Delete(lsel); + // first drop from user accels, if applicable + for(wxAcceleratorEntry_v::iterator i = user_accels.begin(); + i < user_accels.end(); i++) + if(i->GetFlags() == selmod && i->GetKeyCode() == selkey) { + user_accels.erase(i); + break; + } + // if it's a system accel, disable by assigning to NOOP + wxAcceleratorEntry_v &sys_accels = wxGetApp().frame->sys_accels; + for(int i = 0; i < sys_accels.size(); i++) + if(sys_accels[i].GetFlags() == selmod && + sys_accels[i].GetKeyCode() == selkey) { + wxAcceleratorEntry ne(selmod, selkey, XRCID("NOOP")); + user_accels.push_back(ne); + } + // finally, remove from accels instead of recomputing + for(wxAcceleratorEntry_v::iterator i = accels.begin(); + i < accels.end(); i++) + if(i->GetFlags() == selmod && i->GetKeyCode() == selkey) { + accels.erase(i); + break; + } + } + + // wipe out all user bindings + void ResetAll(wxCommandEvent &ev) + { + if(user_accels.empty() || + wxMessageBox(_("This will clear all user-defined accelerators. Are you sure?"), + _("Confirm"), wxYES_NO) != wxYES) + return; + user_accels.clear(); + accels = wxGetApp().frame->sys_accels; + tc->Unselect(); + lb->Clear(); + // rather than recomputing curas, just clear it + key->SetValue(wxT("")); + curas->SetLabel(wxT("")); + } + + // remove old key binding, add new key binding, and update GUI + void Assign(wxCommandEvent &ev) + { + wxTreeItemId csel = tc->GetSelection(); + wxString accel = key->GetValue(); + if(!csel.IsOk() || accel.empty()) + return; + int acmod, ackey; + if(!wxKeyTextCtrl::FromString(accel, acmod, ackey)) + return; // this should never happen + for(int i = 0; i < lb->GetCount(); i++) + if(lb->GetString(i) == accel) + return; // ignore attempts to add twice + lb->Append(accel); + + // first drop from user accels, if applicable + for(wxAcceleratorEntry_v::iterator i = user_accels.begin(); + i < user_accels.end(); i++) + if(i->GetFlags() == acmod && i->GetKeyCode() == ackey) { + user_accels.erase(i); + break; + } + // then assign to this command + const TreeInt *id = static_cast(tc->GetItemData(csel)); + wxAcceleratorEntry ne(acmod, ackey, cmdtab[id->val].cmd_id); + user_accels.push_back(ne); + + // now assigned to this cmd... + wxString lab; + treeid_to_name(id->val, lab, tc, tc->GetRootItem()); + curas->SetLabel(lab); + + // finally, instead of recomputing accels, just append new accel + accels.push_back(ne); + } + + // update curas and maybe enable asb + void CheckKey(wxCommandEvent &ev) + { + wxString nkey = key->GetValue(); + if(nkey.empty()) { + curas->SetLabel(wxT("")); + asb->Enable(false); + return; + } + int acmod, ackey; + if(!wxKeyTextCtrl::FromString(nkey, acmod, ackey)) { + // this should never happen + key->SetValue(wxT("")); + asb->Enable(false); + return; + } + asb->Enable(tc->GetSelection().IsOk()); + int cmd = -1; + for(int i = 0; i < accels.size(); i++) + if(accels[i].GetFlags() == acmod && accels[i].GetKeyCode() == ackey) { + int cmdid = accels[i].GetCommand(); + for(cmd = 0; cmd < ncmds; cmd++) + if(cmdid == cmdtab[cmd].cmd_id) + break; + break; + } + if(cmd < 0 || cmdtab[cmd].cmd_id == XRCID("NOOP")) { + curas->SetLabel(wxT("")); + return; + } + wxString lab; + treeid_to_name(cmd, lab, tc, tc->GetRootItem()); + curas->SetLabel(lab); + } +} accel_config_handler; + +// build initial accel tree control from menu +void MainFrame::add_menu_accels(wxTreeCtrl *tc, wxTreeItemId &parent, wxMenu *menu) +{ + wxMenuItemList mil = menu->GetMenuItems(); + for(wxMenuItemList::iterator mi = mil.begin(); mi != mil.end(); mi++) { + if((*mi)->IsSeparator()) { + tc->AppendItem(parent, wxT("-----")); + } else if((*mi)->IsSubMenu()) { + wxTreeItemId id = tc->AppendItem(parent, (*mi)->GetItemLabelText()); + add_menu_accels(tc, id, (*mi)->GetSubMenu()); + if((*mi)->GetSubMenu() == recent) { + for(int i = wxID_FILE1; i <= wxID_FILE10; i++) { + int cmdid; + for(cmdid = 0; cmdid < ncmds; cmdid++) + if(cmdtab[cmdid].cmd_id == i) + break; + TreeInt *val = new TreeInt(cmdid); + tc->AppendItem(id, cmdtab[cmdid].name, -1, -1, val); + } + } + } else { + int mid = (*mi)->GetId(); + if(mid >= wxID_FILE1 && mid <= wxID_FILE10) + continue; + int cmdid; + for(cmdid = 0; cmdid < ncmds; cmdid++) + if(cmdtab[cmdid].cmd_id == mid) + break; + if(cmdid == ncmds) + continue; // bad menu item; should inform user really + TreeInt *val = new TreeInt(cmdid); + // ugh. There has to be a better way... + // perhaps make XRCID ranges a requirement for load/save st? + // but then if the user overides main menu, that req. is broken.. + wxString txt = (*mi)->GetItemLabelText(); + for(int i = 0; i < 10; i++) + if(*mi == loadst_mi[i] || *mi == savest_mi[i]) { + txt = cmdtab[i].name; + break; + } + tc->AppendItem(parent, txt, -1, -1, val); + } + } +} + +// manage throttle spinctrl/canned setting choice interaction +static class ThrottleCtrl_t : public wxEvtHandler +{ +public: + wxSpinCtrl *thr; + wxChoice *thrsel; + + // set thrsel from thr + void SetThrottleSel(wxSpinEvent &evt) + { + DoSetThrottleSel(thr->GetValue()); + } + + void DoSetThrottleSel(int val) + { + switch(val) { + case 0: + thrsel->SetSelection(1); + break; + case 25: + thrsel->SetSelection(2); + break; + case 50: + thrsel->SetSelection(3); + break; + case 100: + thrsel->SetSelection(4); + break; + case 150: + thrsel->SetSelection(5); + break; + case 200: + thrsel->SetSelection(6); + break; + default: + thrsel->SetSelection(0); + break; + } + } + + // set thr from thrsel + void SetThrottle(wxCommandEvent &evt) + { + switch(thrsel->GetSelection()) { + case 0: // blank; leave it alone + break; + case 1: + thr->SetValue(0); + break; + case 2: + thr->SetValue(25); + break; + case 3: + thr->SetValue(50); + break; + case 4: + thr->SetValue(100); + break; + case 5: + thr->SetValue(150); + break; + case 6: + thr->SetValue(200); + break; + } + } + + // since this is not the actual dialog, derived from wxDialog, which is + // the normal way of doing things, do init on the show event instead of + // constructor + // could've also used a validator, I guess... + void Init(wxShowEvent &ev) + { + ev.Skip(); + DoSetThrottleSel(gopts.throttle); + } +} throttle_ctrl; + +///////////////////////////// + +bool MainFrame::InitMore(void) +{ + // Make sure display panel present and correct type + panel = XRCCTRL(*this, "DisplayArea", GameArea); + if(!panel) { + wxLogError(_("Main display panel not found")); + return false; + } + panel->AdjustSize(false); + + // only the panel does idle events (the emulator loop) + // however, do not enable until end of init, since errors will start + // the idle loop on wxGTK + wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); + + // could/should probably take this from xrc as well + // but I don't think xrc supports icon from Windows resource + wxIcon icon = wxXmlResource::Get()->LoadIcon(wxT("MainIcon")); + if(!icon.IsOk()) { + wxLogInfo(_("Main icon not found")); + icon = wxICON(wxvbam); + } + SetIcon(icon); + + // NOOP if no status area + SetStatusText(_("Welcome to wxVBAM!")); + + // Prepare system accel table + for(int i = 0; i < num_def_accels; i++) + sys_accels.push_back(default_accels[i]); + + // If there is a menubar, store all special menuitems +#define XRCITEM_I(id) menubar->FindItem(id, NULL) +#define XRCITEM_D(s) XRCITEM_I(XRCID_D(s)) +#define XRCITEM(s) XRCITEM_D(wxT(s)) + wxMenuBar *menubar = GetMenuBar(); + ctx_menu = NULL; + if(menubar) { +#if 0 // doesn't work in 2.9 at all (causes main menu to malfunction) + // to fix, recursively copy entire menu insted of just copying + // menubar. This means that every saved menu item must also be + // saved twice... A lot of work for a mostly worthless feature. + // If you want the menu, just exit full-screen mode. + // Either that, or add an option to retain the regular + // menubar in full-screen mode + + // create a context menu for fullscreen mode + // FIXME: on gtk port, this gives Gtk-WARNING **: + // gtk_menu_attach_to_widget(): menu already attached to GtkMenuItem + // but it works anyway + // Note: menu default accelerators (e.g. alt-f for file menu) don't + // work with context menu (and can never work, since there is no + // way to pop up a submenu) + // It would probably be better, in the end, to use a collapsed menu + // bar (either Amiga-style press RMB to make appear, or Windows + // collapsed toolbar-style move mouse to within a pixel of top to + // make appear). Not supported in wx without a lot of work, though. + // Maybe this feature should just be dropped; the user would simply + // have to exit fullscreen mode to use the menu. + ctx_menu = new wxMenu(); + for(int i = 0; i < menubar->GetMenuCount(); i++) + ctx_menu->AppendSubMenu(menubar->GetMenu(i), menubar->GetMenuLabel(i)); +#endif + + // save all menu items in the command table + for(int i = 0; i < ncmds; i++) { + wxMenuItem *mi = cmdtab[i].mi = XRCITEM_I(cmdtab[i].cmd_id); + // remove unsupported commands first +#ifdef NO_FFMPEG + if(cmdtab[i].mask_flags & (CMDEN_SREC|CMDEN_NSREC|CMDEN_VREC|CMDEN_NVREC)) { + if(mi) + mi->GetMenu()->Remove(mi); + cmdtab[i].cmd_id = XRCID("NOOP"); + cmdtab[i].mi = NULL; + continue; + } +#endif +#ifndef GBA_LOGGING + if(cmdtab[i].cmd_id == XRCID("Logging")) { + if(mi) + mi->GetMenu()->Remove(mi); + cmdtab[i].cmd_id = XRCID("NOOP"); + cmdtab[i].mi = NULL; + continue; + } +#endif +#ifdef NO_LINK + if(cmdtab[i].cmd_id == XRCID("LinkConfigure") || + cmdtab[i].cmd_id == XRCID("LanLink")) { + if(mi) + mi->GetMenu()->Remove(mi); + cmdtab[i].cmd_id = XRCID("NOOP"); + cmdtab[i].mi = NULL; + continue; + } +#endif + if(mi) { + // wxgtk provides no way to retrieve stock label/accel + // and does not override wxGetStockLabel() + // as of 2.8.12/2.9.1 + // so override with wx's stock label + // at least you still get gtk's stock icon + if(mi->GetItemLabel().empty()) + mi->SetItemLabel(wxGetStockLabel(mi->GetId(), + wxSTOCK_WITH_MNEMONIC|wxSTOCK_WITH_ACCELERATOR)); + + // add accelerator to global accel table + wxAcceleratorEntry *a = mi->GetAccel(); + if(a) { + a->Set(a->GetFlags(), a->GetKeyCode(), cmdtab[i].cmd_id, mi); + // only add it if not already there + for(wxAcceleratorEntry_v::iterator e = sys_accels.begin(); + e < sys_accels.end(); e++) + if(a->GetFlags() == e->GetFlags() && + a->GetKeyCode() == e->GetKeyCode()) { + if(e->GetMenuItem()) { + wxLogInfo(_("Duplicate menu accelerator: %s for %s and %s; keeping first"), + wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).c_str(), + e->GetMenuItem()->GetItemLabelText().c_str(), + mi->GetItemLabelText().c_str()); + delete a; + a = 0; + } else { + if(e->GetCommand() != a->GetCommand()) { + int cmd; + for(cmd = 0; cmd < ncmds; cmd++) + if(cmdtab[cmd].cmd_id == e->GetCommand()) + break; + wxLogInfo(_("Menu accelerator %s for %s overrides default for %s ; keeping menu"), + wxKeyTextCtrl::ToString(a->GetFlags(), a->GetKeyCode()).c_str(), + mi->GetItemLabelText().c_str(), + cmdtab[cmd].cmd); + } + sys_accels.erase(e); + } + break; + } + if(a) + sys_accels.push_back(*a); + else + // strip from label so user isn't confused + DoSetAccel(mi, NULL); + } + + // store checkable items + if(mi->IsCheckable()) { + checkable_mi_t cmi = { cmdtab[i].cmd_id, mi }; + checkable_mi.push_back(cmi); + } + } + } + + // if a recent menu is present, save its location + wxMenuItem *recentmi = XRCITEM("RecentMenu"); + if(recentmi && recentmi->IsSubMenu()) { + recent = recentmi->GetSubMenu(); + gopts.recent->UseMenu(recent); + gopts.recent->AddFilesToMenu(); + } else + recent = NULL; + // if save/load state menu items present, save their locations + for(int i = 0; i < 10; i++) { + wxString n; + n.Printf(wxT("LoadGame%02d"), i + 1); + loadst_mi[i] = XRCITEM_D(n); + n.Printf(wxT("SaveGame%02d"), i + 1); + savest_mi[i] = XRCITEM_D(n); + } + } else { + recent = NULL; + for(int i = 0; i < 10; i++) + loadst_mi[i] = savest_mi[i] = NULL; + } + // just setting to UNLOAD_CMDEN_KEEP is invalid + // so just set individual flags here + cmd_enable = CMDEN_NGDB_ANY | CMDEN_NREC_ANY; + update_state_ts(true); + enable_menus(); + // set pointers for checkable menu items + // and set initial checked status + if(checkable_mi.size()) { +#define add_bcheck(s, f) do { \ + int id = XRCID(s); \ + for(int i = 0; i < checkable_mi.size(); i++) { \ + if(checkable_mi[i].cmd != id) \ + continue; \ + checkable_mi[i].boolopt = &f; \ + checkable_mi[i].mi->Check(f); \ + break; \ + } \ +} while(0) + +#define add_icheck(s, f, m, v) do { \ + int id = XRCID(s); \ + for(int i = 0; i < checkable_mi.size(); i++) { \ + if(checkable_mi[i].cmd != id) \ + continue; \ + checkable_mi[i].intopt = &f; \ + checkable_mi[i].mask = m; \ + checkable_mi[i].val = v; \ + checkable_mi[i].mi->Check((f & m) == v); \ + break; \ + } \ +} while(0) +#define add_icheck1(s, f, m) add_icheck(s, f, m, m) + add_bcheck("RecentFreeze", gopts.recent_freeze); + add_bcheck("Pause", paused); + add_icheck1("SoundChannel1", gopts.sound_en, (1<<0)); + add_icheck1("SoundChannel2", gopts.sound_en, (1<<1)); + add_icheck1("SoundChannel3", gopts.sound_en, (1<<2)); + add_icheck1("SoundChannel4", gopts.sound_en, (1<<3)); + add_icheck1("DirectSoundA", gopts.sound_en, (1<<8)); + add_icheck1("DirectSoundB", gopts.sound_en, (1<<9)); + add_icheck1("VideoLayersBG0", layerSettings, (1<<8)); + add_icheck1("VideoLayersBG1", layerSettings, (1<<9)); + add_icheck1("VideoLayersBG2", layerSettings, (1<<10)); + add_icheck1("VideoLayersBG3", layerSettings, (1<<11)); + add_icheck1("VideoLayersOBJ", layerSettings, (1<<12)); + add_icheck1("VideoLayersWIN0", layerSettings, (1<<13)); + add_icheck1("VideoLayersWIN1", layerSettings, (1<<14)); + add_icheck1("VideoLayersOBJWIN", layerSettings, (1<<15)); + add_bcheck("CheatsAutoSaveLoad", gopts.autoload_cheats); + add_bcheck("CheatsEnable", cheatsEnabled); + add_bcheck("KeepSaves", skipSaveGameBattery); + add_bcheck("KeepCheats", skipSaveGameCheats); + add_bcheck("LoadGameAutoLoad", gopts.autoload_state); + add_icheck1("JoypadAutofireA", autofire, KEYM_A); + add_icheck1("JoypadAutofireB", autofire, KEYM_B); + add_icheck1("JoypadAutofireL", autofire, KEYM_LEFT); + add_icheck1("JoypadAutofireR", autofire, KEYM_RIGHT); + add_bcheck("EmulatorSpeedupToggle", turbo); + } + for(int i = 0; i < checkable_mi.size(); i++) + if(!checkable_mi[i].boolopt && !checkable_mi[i].intopt) { + wxLogError(_("Invalid menu item %s; removing"), + checkable_mi[i].mi->GetItemLabelText().c_str()); + checkable_mi[i].mi->GetMenu()->Remove(checkable_mi[i].mi); + checkable_mi[i].mi = NULL; + } + for(checkable_mi_array_t::iterator it = checkable_mi.end(); + it != checkable_mi.begin(); it--) + if(!it[-1].mi) + checkable_mi.erase(it-1); + + set_global_accels(); + + // preload and verify all resource dialogs + // this will take init time and memory, but catches errors in xrc sooner + // note that the only verification done is to ensure no crashes. It's the + // user's responsibility to ensure that the GUI works as intended after + // modifications + + wxDialog *d = 0; + const wxChar *dname; +#define baddialog() do { \ + wxLogError(_("Unable to load dialog %s from resources"), dname); \ + return false; \ +} while(0) +#define baddialogcv(n) do { \ + wxLogError(_("Unable to load dialog %s (control %s) from resources"), dname, n); \ + return false; \ +} while(0) +#define baddialogc(n) baddialogcv(wxT(n)) +#define LoadXRCDialog(n) do { \ + /* why do I have to manually Fit()? */ \ + /* since I do, always do it for last item so other init happens first */ \ + /* don't forget to Fit() the last dialog! */ \ + if(d != 0) \ + d->Fit(); \ + dname = wxT(n); \ + /* using this instead of LoadDialog() allows non-wxDialog classes that */ \ + /* are derived from wxDialog (like wxPropertyDialog) to work */ \ + d = wxDynamicCast(wxXmlResource::Get()->LoadObject(this, dname, wxEmptyString), \ + wxDialog); \ + if(!d) \ + baddialog(); \ + /* wx-2.9.1 doesn't set parent for propertysheetdialogs for some reason */ \ + /* this will generate a gtk warning but it is necessary for later */ \ + /* retrieval using FindWindow() */ \ + if(!d->GetParent()) \ + d->Reparent(this); \ + \ + mark_recursive(d); \ +} while(0) + +#define vfld(f, t) do { \ + if(!XRCCTRL(*d, f, t)) \ + baddialogc(f); \ +} while(0) +#define getfld(v, f, t) do { \ + if(!(v = XRCCTRL(*d, f, t))) \ + baddialogc(f); \ +} while(0) +#define getfldv(v, f, t) do { \ + if(!(v = XRCCTRL_D(*d, f, t))) \ + baddialogcv(f.c_str()); \ +} while(0) + + //// displayed during run + LoadXRCDialog("GBPrinter"); + // just verify preview window & mag sel present + { + wxPanel *prev; + getfld(prev, "Preview", wxPanel); + if(!wxDynamicCast(prev->GetParent(), wxScrolledWindow)) + baddialogc("Preview"); + vfld("Magnification", wxControlWithItems); + } + + //// File menu + LoadXRCDialog("GBAROMInfo"); + // just verify fields present + wxControl *lab; +#define getlab(n) getfld(lab, n, wxControl) + getlab("Title"); + getlab("GameCode"); + getlab("MakerCode"); + getlab("MakerName"); + getlab("UnitCode"); + getlab("DeviceType"); + getlab("Version"); + getlab("CRC"); + + LoadXRCDialog("GBROMInfo"); + // just verify fields present + getlab("Title"); + getlab("MakerCode"); + getlab("MakerName"); + getlab("UnitCode"); + getlab("DeviceType"); + getlab("Version"); + getlab("CRC"); + getlab("Color"); + getlab("ROMSize"); + getlab("RAMSize"); + getlab("DestCode"); + getlab("LicCode"); + getlab("Checksum"); + + LoadXRCDialog("CodeSelect"); + // just verify list present + vfld("CodeList", wxControlWithItems); + + LoadXRCDialog("ExportSPS"); + // just verify text fields present + vfld("Title", wxTextCtrl); + vfld("Description", wxTextCtrl); + vfld("Notes", wxTextCtrl); + + //// Emulation menu +#ifndef NO_LINK + LoadXRCDialog("NetLink"); +#endif + wxRadioButton *rb; +#define getrbi(n, o, v) do { \ + getfld(rb, n, wxRadioButton); \ + rb->SetValidator(wxBoolIntValidator(&o, v)); \ +} while(0) +#define getrbb(n, o) do { \ + getfld(rb, n, wxRadioButton); \ + rb->SetValidator(wxGenericValidator(&o)); \ +} while(0) +#define getrbbr(n, o) do { \ + getfld(rb, n, wxRadioButton); \ + rb->SetValidator(wxBoolRevValidator(&o)); \ +} while(0) + wxBoolEnValidator *benval; + wxBoolEnHandler *ben; +#define getbe(n, o, cv, t, wt) do { \ + getfld(cv, n, t); \ + cv->SetValidator(wxBoolEnValidator(&o)); \ + benval = wxStaticCast(cv->GetValidator(), wxBoolEnValidator); \ + static wxBoolEnHandler _ben; \ + ben = &_ben; \ + wx##wt##BoolEnHandlerConnect(cv, wxID_ANY, _ben); \ +} while(0) + // brenval & friends are here just to allow yes/no radioboxes in place + // of checkboxes. A lot of work for little benefit. + wxBoolRevEnValidator *brenval; +#define getbre(n, o, cv, t, wt) do { \ + getfld(cv, n, t); \ + cv->SetValidator(wxBoolRevEnValidator(&o)); \ + brenval = wxStaticCast(cv->GetValidator(), wxBoolRevEnValidator); \ + wx##wt##BoolEnHandlerConnect(rb, wxID_ANY, *ben); \ +} while(0) +#define addbe(n) do { \ + ben->controls.push_back(n); \ + benval->controls.push_back(n); \ +} while(0) +#define addrbe(n) do { \ + addbe(n); \ + brenval->controls.push_back(n); \ +} while(0) +#define addber(n, r) do { \ + ben->controls.push_back(n); \ + ben->reverse.push_back(r); \ + benval->controls.push_back(n); \ + benval->reverse.push_back(r); \ +} while(0) +#define addrber(n, r) do { \ + addber(n, r); \ + brenval->controls.push_back(n); \ + brenval->reverse.push_back(r); \ +} while(0) +#define getrbbe(n, o) getbe(n, o, rb, wxRadioButton, RBE) +#define getrbbd(n, o) getbre(n, o, rb, wxRadioButton, RBD) + wxTextCtrl *tc; +#define gettc(n, o) do { \ + getfld(tc, n, wxTextCtrl); \ + tc->SetValidator(wxTextValidator(wxFILTER_NONE, &o)); \ +} while(0) +#ifndef NO_LINK + { + net_link_handler.dlg = d; + getrbbe("Server", lanlink.server); + getrbbd("Client", lanlink.server); + getlab("PlayersLab"); + addrber(lab, false); + getrbi("Link2P", net_link_handler.n_players, 2); + addrber(rb, false); + getrbi("Link3P", net_link_handler.n_players, 3); + addrber(rb, false); + getrbi("Link4P", net_link_handler.n_players, 4); + addrber(rb, false); + getlab("ServerIPLab"); + addrber(lab, true); + gettc("ServerIP", gopts.link_host); + addrber(tc, true); + tc->SetValidator(IPHostValidator(&gopts.link_host)); + getrbbr("SpeedOff", lanlink.speed); + getrbb("SpeedOn", lanlink.speed); + wxWindow *okb = d->FindWindow(wxID_OK); + if(okb) { // may be gone if style guidlines removed it + net_link_handler.okb = wxStaticCast(okb, wxButton); + d->Connect(XRCID("Server"), wxEVT_COMMAND_RADIOBUTTON_SELECTED, + wxCommandEventHandler(NetLink_t::ServerOKButton), + NULL, &net_link_handler); + d->Connect(XRCID("Client"), wxEVT_COMMAND_RADIOBUTTON_SELECTED, + wxCommandEventHandler(NetLink_t::ClientOKButton), + NULL, &net_link_handler); + } + // this should intercept wxID_OK before the dialog handler gets it + d->Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(NetLink_t::NetConnect), + NULL, &net_link_handler); + } +#endif + + LoadXRCDialog("CheatList"); + { + cheat_list_handler.dlg = d; + d->SetEscapeId(wxID_OK); + wxCheckedListCtrl *cl; + getfld(cl, "Cheats", wxCheckedListCtrl); + if(!cl->Init()) + baddialogc("Cheats"); + cheat_list_handler.list = cl; + cl->SetValidator(CheatListFill()); + cl->InsertColumn(0, _("Code")); + // can't just set font for whole column; must set in each + // individual item + wxFont of = cl->GetFont(); + // of.SetFamily(wxFONTFAMILY_MODERN); // doesn't work (no font change) + wxFont f(of.GetPointSize(), wxFONTFAMILY_MODERN, of.GetStyle(), + of.GetWeight()); + cheat_list_handler.item0.SetFont(f); + cheat_list_handler.item0.SetColumn(0); + cl->InsertColumn(1, _("Description")); + // too bad I can't just set the size to windowwidth - other cols + // default width is header width, but using following will probably + // make it 80 pixels wide regardless + // cl->SetColumnWidth(1, wxLIST_AUTOSIZE_USEHEADER); + cheat_list_handler.col1minw = cl->GetColumnWidth(1); + // on wxGTK, column 1 seems to inherit column 0's font regardless + // of requested font + cheat_list_handler.item1.SetFont(cl->GetFont()); + cheat_list_handler.item1.SetColumn(1); +#if 0 + // the ideal way to set col 0's width would be to use + // wxLIST_AUTOSIZE after setting value to a sample: + cheat_list_handler.item0.SetText(wxT("00000000 00000000")); + cl->InsertItem(cheat_list_handler.item0); + cl->SetColumnWidth(0, wxLIST_AUTOSIZE); + cl->RemoveItem(0); +#else + // however, the generic listctrl implementation uses the wrong + // font to determine width (window vs. item), and does not + // calculate the margins the same way in calculation vs. actual + // drawing. so calculate manually, using knowledge of underlying + // code. This is highly version-unportable, but better than using + // buggy wx code.. + int w, h; + cl->GetImageList(wxIMAGE_LIST_SMALL)->GetSize(0, w, h); + w += 5; // IMAGE_MARGIN_IN_REPORT_MODE + // following is missing from wxLIST_AUTOSIZE + w += 8; // ??? subtracted from width avail for text + { + int charwidth, charheight; + wxClientDC dc(cl); + // following is item font instead of window font, + // and so is missing from wxLIST_AUTOSIZE + dc.SetFont(f); + dc.GetTextExtent(wxT('M'), &charwidth, &charheight); + w += (8 + 1 + 8) * charwidth; + } + cl->SetColumnWidth(0, w); +#endif + + d->Connect(wxEVT_COMMAND_TOOL_CLICKED, + wxCommandEventHandler(CheatList_t::Tool), + NULL, &cheat_list_handler); + d->Connect(wxEVT_COMMAND_LIST_ITEM_CHECKED, + wxListEventHandler(CheatList_t::Check), + NULL, &cheat_list_handler); + d->Connect(wxEVT_COMMAND_LIST_ITEM_UNCHECKED, + wxListEventHandler(CheatList_t::UnCheck), + NULL, &cheat_list_handler); + d->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED, + wxListEventHandler(CheatList_t::Edit), + NULL, &cheat_list_handler); + } + + LoadXRCDialog("CheatEdit"); + wxChoice *ch; +#define getch(n, o) do { \ + getfld(ch, n, wxChoice); \ + ch->SetValidator(wxGenericValidator(&o)); \ +} while(0) + { + // d->Reparent(cheat_list_handler.dlg); // broken + getch("Type", cheat_list_handler.ce_type); + cheat_list_handler.ce_type_ch = ch; + gettc("Desc", cheat_list_handler.ce_desc); + tc->SetMaxLength(sizeof(cheatsList[0].desc) - 1); + gettc("Codes", cheat_list_handler.ce_codes); + cheat_list_handler.ce_codes_tc = tc; + } + + LoadXRCDialog("CheatCreate"); + { + cheat_find_handler.dlg = d; + d->SetEscapeId(wxID_OK); + CheatListCtrl *list; + getfld(list, "CheatList", CheatListCtrl); + cheat_find_handler.list = list; + list->SetValidator(CheatFindFill()); + list->InsertColumn(0, _("Address")); + list->InsertColumn(1, _("Old Value")); + list->InsertColumn(2, _("New Value")); + getrbi("EQ", cheat_find_handler.op, SEARCH_EQ); + getrbi("NE", cheat_find_handler.op, SEARCH_NE); + getrbi("LT", cheat_find_handler.op, SEARCH_LT); + getrbi("LE", cheat_find_handler.op, SEARCH_LE); + getrbi("GT", cheat_find_handler.op, SEARCH_GT); + getrbi("GE", cheat_find_handler.op, SEARCH_GE); +#define cf_make_update() \ + rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \ + wxCommandEventHandler(CheatFind_t::UpdateView), \ + NULL, &cheat_find_handler) + getrbi("Size8", cheat_find_handler.size, BITS_8); + cf_make_update(); + getrbi("Size16", cheat_find_handler.size, BITS_16); + cf_make_update(); + getrbi("Size32", cheat_find_handler.size, BITS_32); + cf_make_update(); + getrbi("Signed", cheat_find_handler.fmt, CFVFMT_SD); + cf_make_update(); + getrbi("Unsigned", cheat_find_handler.fmt, CFVFMT_UD); + cf_make_update(); + getrbi("Hexadecimal", cheat_find_handler.fmt, CFVFMT_UH); + cf_make_update(); +#define cf_make_valen() \ + rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \ + wxCommandEventHandler(CheatFind_t::EnableVal), \ + NULL, &cheat_find_handler) + getrbi("OldValue", cheat_find_handler.valsrc, 1); + cf_make_valen(); + cheat_find_handler.old_rb = rb; + rb->Disable(); + getrbi("SpecificValue", cheat_find_handler.valsrc, 0); + cf_make_valen(); + cheat_find_handler.val_rb = rb; + gettc("Value", cheat_find_handler.val_s); + cheat_find_handler.val_tc = tc; + wxStaticCast(tc->GetValidator(), wxTextValidator)->SetStyle(wxFILTER_INCLUDE_CHAR_LIST); +#define cf_button(n, f) \ + d->Connect(XRCID(n), wxEVT_COMMAND_BUTTON_CLICKED, \ + wxCommandEventHandler(CheatFind_t::f), \ + NULL, &cheat_find_handler); +#define cf_enbutton(n, v) do { \ + getfld(cheat_find_handler.v, n, wxButton); \ + cheat_find_handler.v->Disable(); \ +} while(0) + cf_button("Search", Search); + cf_button("Update", UpdateVals); + cf_enbutton("Update", update_b); + cf_button("Clear", ResetSearch); + cf_enbutton("Clear", clear_b); + cf_button("AddCheat", AddCheatB); + cf_enbutton("AddCheat", add_b); + d->Connect(wxEVT_COMMAND_LIST_ITEM_ACTIVATED, + wxListEventHandler(CheatFind_t::AddCheatL), + NULL, &cheat_find_handler); + d->Connect(wxEVT_COMMAND_LIST_ITEM_SELECTED, + wxListEventHandler(CheatFind_t::Select), + NULL, &cheat_find_handler); + } + + LoadXRCDialog("CheatAdd"); + { + // d->Reparent(cheat_find_handler.dlg); // broken + gettc("Desc", cheat_find_handler.ca_desc); + tc->SetMaxLength(sizeof(cheatsList[0].desc) - 1); + gettc("Value", cheat_find_handler.ca_val); + cheat_find_handler.ca_val_tc = tc; + // MFC interface used this for cheat list's generic code adder as well, + // and made format selectable in interface. I think the plain + // interface is good enough, even though the format for GB cheats + // is non-obvious. Therefore, the format is now just a read-only + // field. + getlab("Format"); + cheat_find_handler.ca_fmt = lab; + getlab("Address"); + cheat_find_handler.ca_addr = lab; + } + + //// config menu + LoadXRCDialog("GeneralConfig"); + wxCheckBox *cb; +#define getcbb(n, o) do { \ + getfld(cb, n, wxCheckBox); \ + cb->SetValidator(wxGenericValidator(&o)); \ +} while(0) + wxSpinCtrl *sc; +#define getsc(n, o) do { \ + getfld(sc, n, wxSpinCtrl); \ + sc->SetValidator(wxGenericValidator(&o)); \ +} while(0) + { + getcbb("PauseWhenInactive", gopts.defocus_pause); + getcbb("ApplyPatches", gopts.apply_patches); + getrbi("PNG", gopts.cap_format, 0); + getrbi("BMP", gopts.cap_format, 1); + getsc("RewindInterval", gopts.rewind_interval); + getsc("Throttle", gopts.throttle); + throttle_ctrl.thr = sc; + getfld(throttle_ctrl.thrsel, "ThrottleSel", wxChoice); + throttle_ctrl.thr-> + Connect(wxEVT_COMMAND_SPINCTRL_UPDATED, + wxSpinEventHandler(ThrottleCtrl_t::SetThrottleSel), + NULL, &throttle_ctrl); + throttle_ctrl.thrsel-> + Connect(wxEVT_COMMAND_CHOICE_SELECTED, + wxCommandEventHandler(ThrottleCtrl_t::SetThrottle), + NULL, &throttle_ctrl); + d->Connect(wxEVT_SHOW, wxShowEventHandler(ThrottleCtrl_t::Init), + NULL, &throttle_ctrl); + } + +#define getcbbe(n, o) getbe(n, o, cb, wxCheckBox, CB) + wxBoolIntEnValidator *bienval; +#define getbie(n, o, v, cv, t, wt) do { \ + getfld(cv, n, t); \ + cv->SetValidator(wxBoolIntEnValidator(&o, v, v)); \ + bienval = wxStaticCast(cv->GetValidator(), wxBoolIntEnValidator); \ + static wxBoolEnHandler _ben; \ + ben = &_ben; \ + wx##wt##BoolEnHandlerConnect(cv, wxID_ANY, _ben); \ +} while(0) +#define addbie(n) do { \ + ben->controls.push_back(n); \ + bienval->controls.push_back(n); \ +} while(0) +#define addbier(n, r) do { \ + ben->controls.push_back(n); \ + ben->reverse.push_back(r); \ + bienval->controls.push_back(n); \ + bienval->reverse.push_back(r); \ +} while(0) +#define getcbie(n, o, v) getbie(n, o, v, cb, wxCheckBox, CB) + wxFilePickerCtrl *fp; +#define getfp(n, o) do { \ + getfld(fp, n, wxFilePickerCtrl); \ + fp->SetValidator(wxFileDirPickerValidator(&o)); \ +} while(0) + LoadXRCDialog("GameBoyConfig"); + { + /// System and Peripherals + getch("System", gbEmulatorType); + // "Display borders" corresponds to 2 variables, so it is handled + // in command handler. Plus making changes might require resizing + // game area. Validation only here. + vfld("Borders", wxChoice); + getcbbe("Printer", gopts.gbprint); + getcbb("PrintGather", gopts.print_auto_page); + addbe(cb); + getcbb("PrintSnap", gopts.print_screen_cap); + addbe(cb); + /// Speed + // AutoSkip/FrameSkip are 2 controls for 1 value. Needs post-process + // to ensure checkbox not ignored + getcbie("FrameSkipAuto", gopts.gb_frameskip, -1); + getsc("FrameSkip", gopts.gb_frameskip); + addbier(sc, true); + getlab("FrameSkipLab"); + addbier(lab, true); + /// Boot ROM + getcbbe("BootRomEn", gopts.gb_use_bios); + getfp("BootRom", gopts.gb_bios); + addbe(fp); + getlab("BootRomLab"); + addbe(lab); + getcbbe("CBootRomEn", gopts.gbc_use_bios); + getfp("CBootRom", gopts.gbc_bios); + addbe(fp); + getlab("CBootRomLab"); + addbe(lab); + /// Custom Colors + getcbb("Color", gbColorOption); + wxFarRadio *r = 0; + for(int i = 0; i < 3; i++) { + wxString pn; + // NOTE: wx2.9.1 behaves differently for referenced nodes + // than 2.8! Unless there is an actual child node, the ID field + // will not be overwritten. This means that there should be a + // dummy child node (e.g. position=(0,0)). If you get + // "Unable to load dialog GameBoyConfig from resources", this is + // probably the reason. + pn.Printf(wxT("cp%d"), i + 1); + wxWindow *w; + getfldv(w, pn, wxWindow); + GBColorConfigHandler[i].p = w; + GBColorConfigHandler[i].pno = i; + wxFarRadio *cb; +#define d w + getfld(cb, "UsePalette", wxFarRadio); + if(r) + cb->SetGroup(r); + else + r = cb; + cb->SetValidator(wxBoolIntValidator(&gbPaletteOption, i)); + getfld(ch, "ColorSet", wxChoice); + GBColorConfigHandler[i].c = ch; + for(int j = 0; j < 8; j++) { + wxString s; + s.Printf(wxT("Color%d"), j); + wxColourPickerCtrl *cp; + getfldv(cp, s, wxColourPickerCtrl); + GBColorConfigHandler[i].cp[j] = cp; + cp->SetValidator(wxColorValidator(&systemGbPalette[i * 8 + j])); + } + w->Connect(wxEVT_COMMAND_CHOICE_SELECTED, + wxCommandEventHandler(GBColorConfig_t::ColorSel), + NULL, &GBColorConfigHandler[i]); + w->Connect(XRCID("Reset"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(GBColorConfig_t::ColorReset), + NULL, &GBColorConfigHandler[i]); + w->Connect(wxID_ANY, wxEVT_COMMAND_COLOURPICKER_CHANGED, + wxCommandEventHandler(GBColorConfig_t::ColorButton), + NULL, &GBColorConfigHandler[i]); +#undef d + } + } + + LoadXRCDialog("GameBoyAdvanceConfig"); + { + /// System and peripherals + getch("SaveType", gopts.save_type); + BatConfigHandler.type = ch; + getch("FlashSize", gopts.flash_size); + BatConfigHandler.size = ch; + d->Connect(XRCID("SaveType"), wxEVT_COMMAND_CHOICE_SELECTED, + wxCommandEventHandler(BatConfig_t::ChangeType), + NULL, &BatConfigHandler); +#define getgbaw(n) do { \ + wxWindow *w = d->FindWindow(XRCID(n)); \ + if(!w) \ + baddialogc(n); \ + w->SetValidator(GBACtrlEnabler()); \ +} while(0) + getgbaw("Detect"); + d->Connect(XRCID("Detect"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(BatConfig_t::Detect), + NULL, &BatConfigHandler); + getcbb("RTC", gopts.rtc); + getcbb("AGBPrinter", gopts.agbprint); + + /// Speed + // AutoSkip/FrameSkip are 2 controls for 1 value. Needs post-process + // to ensure checkbox not ignored + getcbie("FrameSkipAuto", gopts.gba_frameskip, -1); + getsc("FrameSkip", gopts.gba_frameskip); + addbier(sc, true); + getlab("FrameSkipLab"); + addbier(lab, true); + + /// Boot ROM + getcbbe("BootRomEn", gopts.gb_use_bios); + getfp("BootRom", gopts.gb_bios); + addbe(fp); + getlab("BootRomLab"); + addbe(lab); + getcbb("SkipIntro", gopts.skip_intro); + addbe(cb); + // doesn't work right now + cb->Hide(); + + /// Game Overrides + getgbaw("GameSettings"); + // the rest must be filled in by command handler; just validate + vfld("Comment", wxTextCtrl); + vfld("OvRTC", wxChoice); + vfld("OvSaveType", wxChoice); + vfld("OvFlashSize", wxChoice); + vfld("OvMirroring", wxChoice); + } + + LoadXRCDialog("DisplayConfig"); + { + /// On-Screen Display + getch("SpeedIndicator", gopts.osd_speed); + getcbb("NoStatusMsg", gopts.no_osd_status); + getcbb("Transparent", gopts.osd_transparent); + + /// Zoom + // this was a choice, but I'd rather not have to make an off-by-one + // validator just for this, and spinctrl is good enough. + getsc("DefaultScale", gopts.video_scale); + getcbb("RetainAspect", gopts.retain_aspect); + getsc("MaxScale", gopts.max_scale); + // fs modes should be filled in at popup time + // since they may change based on what screen is current + vfld("FullscreenMode", wxChoice); + getcbb("Fullscreen", gopts.fullscreen); + + /// Advanced + getrbi("OutputSimple", gopts.render_method, RND_SIMPLE); + getrbi("OutputOpenGL", gopts.render_method, RND_OPENGL); +#ifdef NO_OGL + rb->Hide(); +#endif + getrbi("OutputCairo", gopts.render_method, RND_CAIRO); +#ifdef NO_CAIRO + rb->Hide(); +#endif + getrbi("OutputDirect3D", gopts.render_method, RND_DIRECT3D); +#if !defined(__WXMSW__) || defined(NO_D3D) || 1 // not implemented + rb->Hide(); +#endif + getcbb("Bilinear", gopts.bilinear); + getcbb("VSync", gopts.vsync); + // FIXME: make cb disabled when not GL or d3d +#define getcbi(n, o, v) do { \ + getfld(cb, n, wxCheckBox); \ + cb->SetValidator(wxBoolIntValidator(&o, v)); \ +} while(0) + int mthr = wxThread::GetCPUCount(); + if(mthr > 8) + mthr = 8; + if(mthr < 0) + mthr = 2; + getcbi("Multithread", gopts.max_threads, mthr); + if(mthr <= 1) + cb->Hide(); +#ifdef MMX + getcbb("MMX", cpu_mmx); +#else + getfld(cb, "MMX", wxCheckBox); + cb->Hide(); +#endif + getch("Filter", gopts.filter); + // these two are filled and/or hidden at dialog load time + wxControl *pll; + wxChoice *pl; + getfld(pll, "PluginLab", wxControl); + getfld(pl, "Plugin", wxChoice); + pll->SetValidator(PluginEnabler()); + pl->SetValidator(PluginListFiller(d, pll, ch)); + PluginEnableHandler.lab = pll; + PluginEnableHandler.ch = pl; + ch->Connect(wxEVT_COMMAND_CHOICE_SELECTED, + wxCommandEventHandler(PluginEnable_t::ToggleChoice), + NULL, &PluginEnableHandler); + getch("IFB", gopts.ifb); + } + + LoadXRCDialog("SoundConfig"); + wxSlider *sl; +#define getsl(n, o) do { \ + getfld(sl, n, wxSlider); \ + sl->SetValidator(wxGenericValidator(&o)); \ +} while(0) + { + /// Basic + getsl("Volume", gopts.sound_vol); + sound_config_handler.vol = sl; + d->Connect(XRCID("Volume100"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(SoundConfig_t::FullVol), + NULL, &sound_config_handler); + getch("Rate", gopts.sound_qual); + + /// Advanced +#define audapi_rb(n, v) do {\ + getrbi(n, gopts.audio_api, v); \ + rb->Connect(wxEVT_COMMAND_RADIOBUTTON_SELECTED, \ + wxCommandEventHandler(SoundConfig_t::SetAPI), \ + NULL, &sound_config_handler); \ +} while(0) + audapi_rb("SDL", AUD_SDL); + audapi_rb("OpenAL", AUD_OPENAL); +#ifdef NO_OAL + rb->Hide(); +#endif + audapi_rb("DirectSound", AUD_DIRECTSOUND); +#ifndef __WXMSW__ + rb->Hide(); +#endif + audapi_rb("XAudio2", AUD_XAUDIO2); +#if !defined(__WXMSW__) || defined(NO_XAUDIO2) + rb->Hide(); +#endif + getfld(sound_config_handler.dev, "Device", wxChoice); + sound_config_handler.dev->SetValidator(SoundConfigLoad()); + getcbb("Upmix", gopts.upmix); + sound_config_handler.umix = cb; +#if !defined(__WXMSW__) || defined(NO_XAUDIO2) + cb->Hide(); +#endif + getcbb("HWAccel", gopts.dsound_hw_accel); + sound_config_handler.hwacc = cb; +#ifndef __WXMSW__ + cb->Hide(); +#endif + getcbb("SyncGameAudio", synchronize); + getsl("Buffers", gopts.audio_buffers); + sound_config_handler.bufs = sl; + getlab("BuffersInfo"); + sound_config_handler.bufinfo = lab; + sl->Connect(wxEVT_SCROLL_CHANGED, + wxCommandEventHandler(SoundConfig_t::AdjustFramesEv), + NULL, &sound_config_handler); + sl->Connect(wxEVT_SCROLL_THUMBTRACK, + wxCommandEventHandler(SoundConfig_t::AdjustFramesEv), + NULL, &sound_config_handler); + sound_config_handler.AdjustFrames(10); + + /// Game Boy + getcbb("GBDeclicking", gopts.gb_declick); + getcbbe("GBEnhanceSound", gb_effects_config.enabled); + wxPanel *p; + getfld(p, "GBEnhanceSoundDep", wxPanel); + addbe(p); + getcbb("GBSurround", gb_effects_config.surround); + getsl("GBEcho", gopts.gb_echo); + getsl("GBStereo", gopts.gb_stereo); + + /// Game Boy Advance + getcbb("GBASoundInterpolation", soundInterpolation); + getsl("GBASoundFiltering", gopts.gba_sound_filter); + } + + wxDirPickerCtrl *dp; +#define getdp(n, o) do { \ + getfld(dp, n, wxDirPickerCtrl); \ + dp->SetValidator(wxFileDirPickerValidator(&o)); \ +} while(0) + LoadXRCDialog("DirectoriesConfig"); + { + getdp("GBARoms", gopts.gba_rom_dir); + getdp("GBRoms", gopts.gb_rom_dir); + getdp("BatSaves", gopts.battery_dir); + getdp("StateSaves", gopts.state_dir); + getdp("Screenshots", gopts.scrshot_dir); + getdp("Recordings", gopts.recording_dir); + } + + LoadXRCDialog("JoypadConfig"); + wxFarRadio *r = 0; + for(int i = 0; i < 4; i++) { + wxString pn; + // NOTE: wx2.9.1 behaves differently for referenced nodes + // than 2.8! Unless there is an actual child node, the ID field + // will not be overwritten. This means that there should be a + // dummy child node (e.g. position=(0,0)). If you get + // "Unable to load dialog JoypadConfig from resources", this is + // probably the reason. + pn.Printf(wxT("joy%d"), i + 1); + wxWindow *w; + getfldv(w, pn, wxWindow); +#define d w + wxFarRadio *cb; + getfld(cb, "DefaultConfig", wxFarRadio); + if(r) + cb->SetGroup(r); + else + r = cb; + cb->SetValidator(wxBoolIntValidator(&gopts.default_stick, i + 1)); + wxWindow *prev = NULL, *prevp = NULL; + for(int j = 0; j < NUM_KEYS; j++) { + wxJoyKeyTextCtrl *tc = XRCCTRL_D(*w, joynames[j], wxJoyKeyTextCtrl); + if(!tc) + baddialogcv(joynames[j]); + wxWindow *p = tc->GetParent(); + if(p == prevp) + tc->MoveAfterInTabOrder(prev); + prev = tc; + prevp = p; + tc->SetValidator(wxJoyKeyValidator(&gopts.joykey_bindings[i][j])); + } + JoyPadConfigHandler[i].p = w; + w->Connect(XRCID("Defaults"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons), + NULL, &JoyPadConfigHandler[i]); + w->Connect(XRCID("Clear"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons), + NULL, &JoyPadConfigHandler[i]); +#undef d + } + +#ifndef NO_LINK + LoadXRCDialog("LinkConfig"); + { + getcbbe("Joybus", gba_joybus_enabled); + getlab("JoybusHostLab"); + addbe(lab); + gettc("JoybusHost", gopts.joybus_host); + tc->SetValidator(IPHostValidator(&gopts.joybus_host)); + addbe(tc); + getcbbe("Link", gba_link_enabled); + getcbb("RFU", rfu_enabled); + addbe(cb); + getlab("LinkTimeoutLab"); + addbe(lab); + getsc("LinkTimeout", linktimeout); + addbe(sc); + } +#endif + + LoadXRCDialog("AccelConfig"); + { + wxTreeCtrl *tc; + getfld(tc, "Commands", wxTreeCtrl); + accel_config_handler.tc = tc; + wxControlWithItems *lb; + getfld(lb, "Current", wxControlWithItems); + accel_config_handler.lb = lb; + getfld(accel_config_handler.asb, "Assign", wxButton); + getfld(accel_config_handler.remb, "Remove", wxButton); + getfld(accel_config_handler.key, "Shortcut", wxKeyTextCtrl); + getfld(accel_config_handler.curas, "AlreadyThere", wxControl); + accel_config_handler.key->MoveBeforeInTabOrder(accel_config_handler.asb); + accel_config_handler.key->SetMultikey(0); + accel_config_handler.key->SetClearable(false); + wxTreeItemId rid = tc->AddRoot(wxT("root")); + if(menubar) { + wxTreeItemId mid = tc->AppendItem(rid, _("Menu commands")); + for(int i = 0; i < menubar->GetMenuCount(); i++) { +#if wxCHECK_VERSION(2,8,8) + wxTreeItemId id = tc->AppendItem(mid, menubar->GetMenuLabelText(i)); +#else + // 2.8.4 has no equivalent for GetMenuLabelText() + wxString txt = menubar->GetMenuLabel(i); + txt.Replace(wxT("&"), wxT("")); + wxTreeItemId id = tc->AppendItem(mid, txt); +#endif + add_menu_accels(tc, id, menubar->GetMenu(i)); + } + } + wxTreeItemId oid; + int noop_id = XRCID("NOOP"); + for(int i = 0; i < ncmds; i++) { + if(cmdtab[i].mi || (recent && cmdtab[i].cmd_id >= wxID_FILE1 && + cmdtab[i].cmd_id <= wxID_FILE10) || + cmdtab[i].cmd_id == noop_id) + continue; + if(!oid.IsOk()) + oid = tc->AppendItem(rid, _("Other commands")); + TreeInt *val = new TreeInt(i); + tc->AppendItem(oid, cmdtab[i].name, -1, -1, val); + } + tc->ExpandAll(); + // FIXME: make this actually show the entire line w/o scrolling + // BestSize cuts off on rhs; MaxSize is completely invalid + wxSize sz = tc->GetBestSize(); + if(sz.GetHeight() > 200) + sz.SetHeight(200); + tc->SetSize(sz); + sz.SetWidth(-1); // maybe allow it to become bigger + tc->SetSizeHints(sz, sz); + int w, h; + lb->GetTextExtent(wxT("CTRL-ALT-SHIFT-ENTER"), &w, &h); + sz.Set(w, h); + lb->SetMinSize(sz); + sz.Set(0, 0); + wxControl *curas = accel_config_handler.curas; + for(int i = 0; i < ncmds; i++) { + wxString labs; + treeid_to_name(i, labs, tc, tc->GetRootItem()); + curas->GetTextExtent(labs, &w, &h); + if(w > sz.GetWidth()) + sz.SetWidth(w); + if(h > sz.GetHeight()) + sz.SetHeight(h); + } + curas->SetSize(sz); + curas->SetSizeHints(sz); + tc->Connect(wxEVT_COMMAND_TREE_SEL_CHANGING, + wxTreeEventHandler(AccelConfig_t::CommandSel), + NULL, &accel_config_handler); + tc->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED, + wxTreeEventHandler(AccelConfig_t::CommandSel), + NULL, &accel_config_handler); + d->Connect(wxEVT_SHOW, wxShowEventHandler(AccelConfig_t::Init), + NULL, &accel_config_handler); + d->Connect(wxID_OK, wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(AccelConfig_t::Set), + NULL, &accel_config_handler); + d->Connect(XRCID("Assign"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(AccelConfig_t::Assign), + NULL, &accel_config_handler); + d->Connect(XRCID("Remove"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(AccelConfig_t::Remove), + NULL, &accel_config_handler); + d->Connect(XRCID("ResetAll"), wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(AccelConfig_t::ResetAll), + NULL, &accel_config_handler); + lb->Connect(wxEVT_COMMAND_LISTBOX_SELECTED, + wxCommandEventHandler(AccelConfig_t::KeySel), + NULL, &accel_config_handler); + d->Connect(XRCID("Shortcut"), wxEVT_COMMAND_TEXT_UPDATED, + wxCommandEventHandler(AccelConfig_t::CheckKey), + NULL, &accel_config_handler); + } + + d->Fit(); + + //// Debug menu + // actually, the viewers can be instantiated multiple times. + // since they're for debugging, it's probably OK to just detect errors + // at popup time. + // The only one that can only be popped up once is logging, so allocate + // and check it already. + logdlg = new LogDialog; + + // activate OnDropFile event handler +#if !defined(__WXGTK__) || wxCHECK_VERSION(2,8,10) + // may not actually do anything, but verfied to work w/ Linux/Nautilus + DragAcceptFiles(true); +#endif + + // delayed fullscreen + if(wxGetApp().pending_fullscreen || gopts.fullscreen) + panel->ShowFullScreen(true); + +#ifndef NO_LINK + if(gba_joybus_enabled) { + bool isv = !gopts.joybus_host.empty(); + if(isv) { + joybusHostAddr = std::string(gopts.joybus_host.mb_str()); + isv = joybusHostAddr.IsValid(); + } + if(!isv) { + wxLogError(_("JoyBus host invalid; disabling")); + gba_joybus_enabled = false; + } else + JoyBusConnect(); + } + if(gba_link_enabled) + if((did_link_init = InitLink())) + cmd_enable |= CMDEN_LINK_ANY; + +#endif + panel->SetFrameTitle(); + + // All OK; activate idle loop + panel->SetExtraStyle(panel->GetExtraStyle() | wxWS_EX_PROCESS_IDLE); + + return true; +} + + +wxAcceleratorEntry_v MainFrame::get_accels(wxAcceleratorEntry_v user_accels) +{ + // set global accelerators + // first system + wxAcceleratorEntry_v accels = sys_accels; + // then user overrides + // silently keep only last defined binding + // same horribly inefficent O(n*m) search for duplicates as above.. + for(int i = 0; i < user_accels.size(); i++) { + const wxAcceleratorEntry &ae = user_accels[i]; + for(wxAcceleratorEntry_v::iterator e = accels.begin(); e < accels.end(); e++) + if(ae.GetFlags() == e->GetFlags() && + ae.GetKeyCode() == e->GetKeyCode()) { + accels.erase(e); + break; + } + accels.push_back(ae); + } + return accels; +} + +void MainFrame::set_global_accels() +{ + wxAcceleratorEntry_v accels = get_accels(gopts.accels); + + // this is needed on Wine/win32 to support accels for close & quit + wxGetApp().accels = accels; + + // Update menus; this probably takes the longest + // as a side effect, any system-defined accels that weren't already in + // the menus will be added now + + // first, zero out menu item on all accels + for(int i = 0; i < accels.size(); i++) + accels[i].Set(accels[i].GetFlags(), accels[i].GetKeyCode(), + accels[i].GetCommand()); + + // yet another O(n*m) loop. I really ought to sort the accel arrays + for(int i = 0; i < ncmds; i++) { + wxMenuItem *mi = cmdtab[i].mi; + if(!mi) + continue; + // only *last* accelerator is made visible in menu + // and is flagged as such by setting menu item in accel + // the last is chosen so menu overrides non-menu and user overrides + // system + int cmd = cmdtab[i].cmd_id; + int last_accel = -1; + for(int j = 0; j < accels.size(); j++) + if(cmd == accels[j].GetCommand()) + last_accel = j; + if(last_accel >= 0) { + DoSetAccel(mi, &accels[last_accel]); + accels[last_accel].Set(accels[last_accel].GetFlags(), + accels[last_accel].GetKeyCode(), + accels[last_accel].GetCommand(), mi); + } else + // clear out user-cleared menu items + DoSetAccel(mi, NULL); + } + // Finally, install a global accelerator table for any non-menu accels + int len = 0; + for(int i = 0; i < accels.size(); i++) + if(!accels[i].GetMenuItem()) + len++; + if(len) { + wxAcceleratorEntry tab[len]; + for(int i = 0, j = 0; i < accels.size(); i++) + if(!accels[i].GetMenuItem()) + tab[j++] = accels[i]; + wxAcceleratorTable atab(len, tab); + // set the table on the panel, where focus usually is + // otherwise accelerators are lost sometimes + panel->SetAcceleratorTable(atab); + } else + panel->SetAcceleratorTable(wxNullAcceleratorTable); + + // save recent accels + for(int i = 0; i < 10; i++) + recent_accel[i] = wxAcceleratorEntry(); + for(int i = 0; i < accels.size(); i++) + if(accels[i].GetCommand() >= wxID_FILE1 && + accels[i].GetCommand() <= wxID_FILE10) + recent_accel[accels[i].GetCommand() - wxID_FILE1] = accels[i]; + SetRecentAccels(); +} diff --git a/src/wx/ioregs.h b/src/wx/ioregs.h new file mode 100644 index 00000000..1ea3097b --- /dev/null +++ b/src/wx/ioregs.h @@ -0,0 +1,2072 @@ +// this is essentially a copy of ../win32/IOViewRegisters.h using translatable +// strings and more consts +struct IOData { + u16 *address; + u16 offset; + const wxChar *name; + u16 write; + const wxChar *bits[16]; +}; + +/* const */ IOData ioregs[] = { // not const so tranlation can be done once + { + &DISPCNT, 0, wxTRANSLATE("0x4000000-DISPCNT"), 0xFFF7, + { + wxT(""), + wxT(""), + wxTRANSLATE("BG Mode (3 bits)"), + wxTRANSLATE("CGB Mode"), + wxTRANSLATE("Display Frame"), + wxTRANSLATE("H-Blank Interval OBJ processing"), + wxTRANSLATE("OBJ Character mapping"), + wxTRANSLATE("Forced blank"), + wxTRANSLATE("BG0"), + wxTRANSLATE("BG1"), + wxTRANSLATE("BG2"), + wxTRANSLATE("BG3"), + wxTRANSLATE("OBJ"), + wxTRANSLATE("WIN0"), + wxTRANSLATE("WIN1"), + wxTRANSLATE("OBJWIN") + } + }, + { + &DISPSTAT, 4, wxTRANSLATE("0x4000004-DISPSTAT"), 0xFF38, + { + wxTRANSLATE("V-Blank Status"), + wxTRANSLATE("H-Blank Status"), + wxTRANSLATE("VCOUNT Evaluation"), + wxTRANSLATE("V-Blank Interrupt Enable"), + wxTRANSLATE("H-Blank Interrupt Enable"), + wxTRANSLATE("VCOUNT Match Interrupt Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("VCOUNT setting (8 bits)") + } + }, + { + &VCOUNT, 6, wxTRANSLATE("0x4000006-VCOUNT"), 0x0000, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("VCOUNT (8 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG0CNT, 8, wxTRANSLATE("0x4000008-BG0CNT"), 0xDFCF, + { + wxT(""), + wxTRANSLATE("Priority (2 bits)"), + wxT(""), + wxTRANSLATE("Char base (2 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Mosaic"), + wxTRANSLATE("16/256 colors"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Screen Base Block (5 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Size (2 bits)") + } + }, + { + &BG1CNT, 0xA, wxTRANSLATE("0x400000A-BG1CNT"), 0xDFCF, + { + wxT(""), + wxTRANSLATE("Priority (2 bits)"), + wxT(""), + wxTRANSLATE("Char base (2 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Mosaic"), + wxTRANSLATE("16/256 colors"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Screen Base Block (5 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Size (2 bits)") + } + }, + { + &BG2CNT, 0xC, wxTRANSLATE("0x400000C-BG2CNT"), 0xFFCF, + { + wxT(""), + wxTRANSLATE("Priority (2 bits)"), + wxT(""), + wxTRANSLATE("Char base (2 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Mosaic"), + wxTRANSLATE("16/256 colors"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Screen Base Block (5 bits)"), + wxTRANSLATE("Area Overflow"), + wxT(""), + wxTRANSLATE("Size (2 bits)") + } + }, + { + &BG3CNT, 0xE, wxTRANSLATE("0x400000E-BG3CNT"), 0xFFCF, + { + wxT(""), + wxTRANSLATE("Priority (2 bits)"), + wxT(""), + wxTRANSLATE("Char base (2 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Mosaic"), + wxTRANSLATE("16/256 colors"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Screen Base Block (5 bits)"), + wxTRANSLATE("Area Overflow"), + wxT(""), + wxTRANSLATE("Size (2 bits)") + } + }, + { + &BG0HOFS, 0x10, wxTRANSLATE("0x4000010-BG0HOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Horizontal Offset (9 bits, W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG0VOFS, 0x12, wxTRANSLATE("0x4000012-BG0VOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Vertical Offset (9 bits, W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG1HOFS, 0x14, wxTRANSLATE("0x4000014-BG1HOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Horizontal Offset (9 bits, W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG1VOFS, 0x16, wxTRANSLATE("0x4000016-BG1VOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Vertical Offset (9 bits, W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG2HOFS, 0x18, wxTRANSLATE("0x4000018-BG2HOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Horizontal Offset (9 bits, W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG2VOFS, 0x1A, wxTRANSLATE("0x400001A-BG2VOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Vertical Offset (9 bits, W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG3HOFS, 0x1C, wxTRANSLATE("0x400001C-BG3HOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Horizontal Offset (9 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG3VOFS, 0x1E, wxTRANSLATE("0x400001E-BG3VOFS"), 0x01FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Vertical Offset (9 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT("") + } + }, + { + &BG2PA, 0x20, wxTRANSLATE("0x4000020-BG2PA"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dx (16 bits,W)") + } + }, + { + &BG2PB, 0x22, wxTRANSLATE("0x4000022-BG2PB"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dmx (16 bits,W)") + } + }, + { + &BG2PC, 0x24, wxTRANSLATE("0x4000024-BG2PC"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dy (16 bits,W)") + } + }, + { + &BG2PD, 0x26, wxTRANSLATE("0x4000026-BG2PD"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dmy (16 bits,W)") + } + }, + { + &BG2X_L, 0x28, wxTRANSLATE("0x4000028-BG2X_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("X low bits (16 bits,W)") + } + }, + { + &BG2X_H, 0x2A, wxTRANSLATE("0x400002A-BG2X_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("X high bits (12 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &BG2Y_L, 0x2C, wxTRANSLATE("0x400002C-BG2Y_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Y low bits (16 bits,W)") + } + }, + { + &BG2Y_H, 0x2E, wxTRANSLATE("0x400002E-BG2Y_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Y hight bits (12 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &BG3PA, 0x30, wxTRANSLATE("0x4000030-BG3PA"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dx (16 bits,W)") + } + }, + { + &BG3PB, 0x32, wxTRANSLATE("0x4000032-BG3PB"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dmx (16 bits,W)") + } + }, + { + &BG3PC, 0x34, wxTRANSLATE("0x4000034-BG3PC"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dy (16 bits,W)") + } + }, + { + &BG3PD, 0x36, wxTRANSLATE("0x4000036-BG3PD"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("dmy (16 bits,W)") + } + }, + { + &BG3X_L, 0x38, wxTRANSLATE("0x4000038-BG3X_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("X low bits (16 bits,W)") + } + }, + { + &BG3X_H, 0x3A, wxTRANSLATE("0x400003A-BG3X_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("X hight bits (12 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &BG3Y_L, 0x3C, wxTRANSLATE("0x400003C-BG3Y_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Y low bits (16 bits,W)") + } + }, + { + &BG3Y_H, 0x3E, wxTRANSLATE("0x400003E-BG3Y_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Y hight bits (12 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &WIN0H, 0x40, wxTRANSLATE("0x4000040-WIN0H"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 0 lower-right X (8 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 0 upper-left X (8 bits,W)"), + } + }, + { + &WIN1H, 0x42, wxTRANSLATE("0x4000042-WIN1H"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 1 lower-right X (8 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 1 upper-left (8 bits,W)"), + } + }, + { + &WIN0V, 0x44, wxTRANSLATE("0x4000044-WIN0V"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 0 lower-right Y (8 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 0 upper-left Y (8 bits,W)"), + } + }, + { + &WIN1V, 0x46, wxTRANSLATE("0x4000046-WIN1V"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 1 lower-right Y (8 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Win 1 upper-left Y (8 bits,W)"), + } + }, + { + &WININ, 0x48, wxTRANSLATE("0x4000048-WININ"), 0x3F3F, + { + wxTRANSLATE("WIN0 BG0"), + wxTRANSLATE("WIN0 BG1"), + wxTRANSLATE("WIN0 BG2"), + wxTRANSLATE("WIN0 BG3"), + wxTRANSLATE("WIN0 OBJ"), + wxTRANSLATE("WIN0 Special FX"), + wxT(""), + wxT(""), + wxTRANSLATE("WIN1 BG0"), + wxTRANSLATE("WIN1 BG1"), + wxTRANSLATE("WIN1 BG2"), + wxTRANSLATE("WIN1 BG3"), + wxTRANSLATE("WIN1 OBJ"), + wxTRANSLATE("WIN1 Special FX"), + wxT(""), + wxT(""), + } + }, + { + &WINOUT, 0x4A, wxTRANSLATE("0x400004A-WINOUT"), 0x3F3F, + { + wxTRANSLATE("WIN0/1 BG0"), + wxTRANSLATE("WIN0/1 BG1"), + wxTRANSLATE("WIN0/1 BG2"), + wxTRANSLATE("WIN0/1 BG3"), + wxTRANSLATE("WIN0/1 OBJ"), + wxTRANSLATE("WIN0/1 Special FX"), + wxT(""), + wxT(""), + wxTRANSLATE("OBJWIN BG0"), + wxTRANSLATE("OBJWIN BG1"), + wxTRANSLATE("OBJWIN BG2"), + wxTRANSLATE("OBJWIN BG3"), + wxTRANSLATE("OBJWIN OBJ"), + wxTRANSLATE("OBJWIN Special FX"), + wxT(""), + wxT(""), + } + }, + { + &MOSAIC, 0x4C, wxTRANSLATE("0x400004C-MOSAIC"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("BG H Size (4 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("BG V Size (4 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("OBJ H Size (4 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("OBJ V Size (4 bits,W)"), + } + }, + { + &BLDMOD, 0x50, wxTRANSLATE("0x4000050-BLDMOD"), 0x3FFF, + { + wxTRANSLATE("1st BG0"), + wxTRANSLATE("1st BG1"), + wxTRANSLATE("1st BG2"), + wxTRANSLATE("1st BG3"), + wxTRANSLATE("1st OBJ"), + wxTRANSLATE("1st BD"), + wxT(""), + wxTRANSLATE("FX Type (2 bits)"), + wxTRANSLATE("2nd BG0"), + wxTRANSLATE("2nd BG1"), + wxTRANSLATE("2nd BG2"), + wxTRANSLATE("2nd BG3"), + wxTRANSLATE("2nd OBJ"), + wxTRANSLATE("2nd BD"), + wxT(""), + wxT(""), + } + }, + { + &COLEV, 0x52, wxTRANSLATE("0x4000052-COLEV"), 0x1F1F, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Coefficient EVA (5 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Coefficient EVB (5 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &COLY, 0x54, wxTRANSLATE("0x4000054-COLEY"), 0x001F, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Coefficient EVY (5 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x60, wxTRANSLATE("0x4000060-SG10_L"), 0x007F, + { + wxT(""), + wxT(""), + wxTRANSLATE("Sweep Shifts (3 bits)"), + wxTRANSLATE("Sweep addition/decrease"), + wxT(""), + wxT(""), + wxTRANSLATE("Sweep Time (3 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x62, wxTRANSLATE("0x4000062-SG10_H"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Length (6 bits,W)"), + wxT(""), + wxTRANSLATE("Waveform Type (2 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Envelope Steps (3 bits)"), + wxTRANSLATE("Envelope Attenuate/Amplify"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Envelope Initial Value"), + } + }, + { + NULL, 0x64, wxTRANSLATE("0x4000064-SG11"), 0xC7FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Frequency (11 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Continuous/Counter"), + wxTRANSLATE("Initialization (W)"), + } + }, + { + NULL, 0x68, wxTRANSLATE("0x4000068-SG20"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Length (6 bits,W)"), + wxT(""), + wxTRANSLATE("Waveform Type (2 bits)"), + wxT(""), + wxT(""), + wxTRANSLATE("Envelope Steps (3 bits)"), + wxTRANSLATE("Envelope Attenuate/Amplify"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Envelope Initial Value"), + } + }, + { + NULL, 0x6C, wxTRANSLATE("0x400006C-SG21"), 0xC7FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Frequency (11 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Continuous/Counter"), + wxTRANSLATE("Initialization (W)"), + } + }, + { + NULL, 0x70, wxTRANSLATE("0x4000070-SG30_L"), 0x00E0, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Waveform 32/64 Steps"), + wxTRANSLATE("Waveform Bank 0/1"), + wxTRANSLATE("Sound Output"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x72, wxTRANSLATE("0x4000072-SG30_H"), 0xE0FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Length (8 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Output Level (2 bits)"), + wxTRANSLATE("Forced 3/4 Output Level"), + } + }, + { + NULL, 0x74, wxTRANSLATE("0x4000074-SG31"), 0xC7FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Frequency (11 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Continuous/Counter"), + wxTRANSLATE("Initialization (W)"), + } + }, + { + NULL, 0x78, wxTRANSLATE("0x4000078-SG40"), 0xFF3F, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Length (6 bits,W)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Envelope Steps (3 bits)"), + wxTRANSLATE("Envelope Attenuate/Amplify"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Envelope Initial Value"), + } + }, + { + NULL, 0x7C, wxTRANSLATE("0x400007C-SG41"), 0xC0FF, + { + wxT(""), + wxT(""), + wxTRANSLATE("Dividing Ratio Freq. (3 bits)"), + wxTRANSLATE("Counter 15/7 Steps"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Counter Shift Clock (4 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sound Continuous/Counter"), + wxTRANSLATE("Initialization (W)"), + } + }, + { + NULL, 0x80, wxTRANSLATE("0x4000080-SGCNT0_L"), 0xFF77, + { + wxT(""), + wxT(""), + wxTRANSLATE("Right Volume (3 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Left Volume (3 bits)"), + wxT(""), + wxTRANSLATE("Channel 1->Right"), + wxTRANSLATE("Channel 2->Right"), + wxTRANSLATE("Channel 3->Right"), + wxTRANSLATE("Channel 4->Right"), + wxTRANSLATE("Channel 1->Left"), + wxTRANSLATE("Channel 2->Left"), + wxTRANSLATE("Channel 3->Left"), + wxTRANSLATE("Channel 4->Left"), + } + }, + { + NULL, 0x82, wxTRANSLATE("0x4000082-SGCNT0_H"), 0xFF0F, + { + wxT(""), + wxTRANSLATE("Sound 1-4 Volume (2 bits)"), + wxTRANSLATE("DMA Sound A Volume"), + wxTRANSLATE("DMA Sound B Volume"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("DMA Sound A->Right"), + wxTRANSLATE("DMA Sound A->Left"), + wxTRANSLATE("DMA Sound A Timer"), + wxTRANSLATE("DMA Sound A Reset FIFO"), + wxTRANSLATE("DMA Sound B->Right"), + wxTRANSLATE("DMA Sound B->Left"), + wxTRANSLATE("DMA Sound B Timer"), + wxTRANSLATE("DMA Sound B Reset FIFO"), + } + }, + { + NULL, 0x84, wxTRANSLATE("0x4000084-SGCNT1"), 0x0080, + { + wxTRANSLATE("Sound 1 On"), + wxTRANSLATE("Sound 2 On"), + wxTRANSLATE("Sound 3 On"), + wxTRANSLATE("Sound 4 On"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Master Sound Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x88, wxTRANSLATE("0x4000088-SGBIAS"), 0xC3FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Bias Level (10 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Sampling Rate (2 bits)"), + } + }, + { + NULL, 0xA0, wxTRANSLATE("0x40000A0-SIGFIFOA_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 0 (8 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 1 (8 bits)"), + } + }, + { + NULL, 0xA2, wxTRANSLATE("0x40000A2-SIGFIFOA_H"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 2 (8 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 3 (8 bits)"), + } + }, + { + NULL, 0xA4, wxTRANSLATE("0x40000A4-SIGFIFOB_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 0 (8 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 1 (8 bits)"), + } + }, + { + NULL, 0xA6, wxTRANSLATE("0x40000A6-SIGFIFOB_H"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 2 (8 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Data 3 (8 bits)"), + } + }, + { + &DM0SAD_L, 0xB0, wxTRANSLATE("0x40000B0-DM0SAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (lower 16 bits)"), + } + }, + { + &DM0SAD_H, 0xB2, wxTRANSLATE("0x40000B2-DM0SAD_H"), 0x07FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (upper 11 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM0DAD_L, 0xB4, wxTRANSLATE("0x40000B4-DM0DAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (lower 16 bits)"), + } + }, + { + &DM0DAD_H, 0xB6, wxTRANSLATE("0x40000B6-DM0DAD_H"), 0x07FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (upper 11 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM0CNT_L, 0xB8, wxTRANSLATE("0x40000B8-DM0CNT_L"), 0x3FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Count (14 bits)"), + wxT(""), + wxT(""), + } + }, + { + &DM0CNT_H, 0xBA, wxTRANSLATE("0x40000BA-DM0CNT_H"), 0xF7E0, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address Control (2 bits)"), + wxT(""), + wxTRANSLATE("Source Address Control (2 bits)"), + wxTRANSLATE("Repeat"), + wxTRANSLATE("Transfer Type"), + wxT(""), + wxT(""), + wxTRANSLATE("Start Timing (2 bits)"), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + } + }, + { + &DM1SAD_L, 0xBC, wxTRANSLATE("0x40000BC-DM1SAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (lower 16 bits)"), + } + }, + { + &DM1SAD_H, 0xBE, wxTRANSLATE("0x40000BE-DM1SAD_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (upper 12 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM1DAD_L, 0xC0, wxTRANSLATE("0x40000C0-DM1DAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (lower 16 bits)"), + } + }, + { + &DM1DAD_H, 0xC2, wxTRANSLATE("0x40000C2-DM1DAD_H"), 0x07FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (upper 11 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM1CNT_L, 0xC4, wxTRANSLATE("0x40000C4-DM1CNT_L"), 0x3FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Count (14 bits)"), + wxT(""), + wxT(""), + } + }, + { + &DM1CNT_H, 0xC6, wxTRANSLATE("0x40000C6-DM1CNT_H"), 0xF7E0, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address Control (2 bits)"), + wxT(""), + wxTRANSLATE("Source Address Control (2 bits)"), + wxTRANSLATE("Repeat"), + wxTRANSLATE("Transfer Type"), + wxT(""), + wxT(""), + wxTRANSLATE("Start Timing (2 bits)"), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + } + }, + { + &DM2SAD_L, 0xC8, wxTRANSLATE("0x40000C8-DM2SAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (lower 16 bits)"), + } + }, + { + &DM2SAD_H, 0xCA, wxTRANSLATE("0x40000CA-DM2SAD_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (upper 12 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM2DAD_L, 0xCC, wxTRANSLATE("0x40000CC-DM2DAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (lower 16 bits)"), + } + }, + { + &DM2DAD_H, 0xCE, wxTRANSLATE("0x40000CE-DM2DAD_H"), 0x07FF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (upper 11 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM2CNT_L, 0xD0, wxTRANSLATE("0x40000D0-DM2CNT_L"), 0x3FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Count (14 bits)"), + wxT(""), + wxT(""), + } + }, + { + &DM2CNT_H, 0xD2, wxTRANSLATE("0x40000D2-DM2CNT_H"), 0xF7E0, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address Control (2 bits)"), + wxT(""), + wxTRANSLATE("Source Address Control (2 bits)"), + wxTRANSLATE("Repeat"), + wxTRANSLATE("Transfer Type"), + wxT(""), + wxT(""), + wxTRANSLATE("Start Timing (2 bits)"), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + } + }, + { + &DM3SAD_L, 0xD4, wxTRANSLATE("0x40000D4-DM3SAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (lower 16 bits)"), + } + }, + { + &DM3SAD_H, 0xD6, wxTRANSLATE("0x40000D6-DM3SAD_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Source Address (upper 12 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM3DAD_L, 0xD8, wxTRANSLATE("0x40000D8-DM3DAD_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (lower 16 bits)"), + } + }, + { + &DM3DAD_H, 0xDA, wxTRANSLATE("0x40000DA-DM3DAD_H"), 0x0FFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address (upper 12 bits)"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &DM3CNT_L, 0xDC, wxTRANSLATE("0x40000DC-DM3CNT_L"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Count (16 bits)"), + } + }, + { + &DM3CNT_H, 0xDE, wxTRANSLATE("0x40000DE-DM3CNT_H"), 0xFFE0, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Destination Address Control (2 bits)"), + wxT(""), + wxTRANSLATE("Source Address Control (2 bits)"), + wxTRANSLATE("Repeat"), + wxTRANSLATE("Transfer Type"), + wxTRANSLATE("Game Pak Data Request"), + wxT(""), + wxTRANSLATE("Start Timing (2 bits)"), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + } + }, + { + &TM0D, 0x100, wxTRANSLATE("0x4000100-TM0D"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Timer Counter (16 bits)"), + } + }, + { + &TM0CNT, 0x102, wxTRANSLATE("0x4000102-TM0CNT"), 0x00C7, + { + wxT(""), + wxTRANSLATE("Scalar Selection (2 bits)"), + wxTRANSLATE("Count Up"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &TM1D, 0x104, wxTRANSLATE("0x4000104-TM1D"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Timer Counter (16 bits)"), + } + }, + { + &TM1CNT, 0x106, wxTRANSLATE("0x4000106-TM1CNT"), 0x00C7, + { + wxT(""), + wxTRANSLATE("Scalar Selection (2 bits)"), + wxTRANSLATE("Count Up"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &TM2D, 0x108, wxTRANSLATE("0x4000108-TM2D"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Timer Counter (16 bits)"), + } + }, + { + &TM2CNT, 0x10A, wxTRANSLATE("0x400010A-TM2CNT"), 0x00C7, + { + wxT(""), + wxTRANSLATE("Scalar Selection (2 bits)"), + wxTRANSLATE("Count Up"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &TM3D, 0x10C, wxTRANSLATE("0x400010C-TM3D"), 0xFFFF, + { + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Timer Counter (16 bits)"), + } + }, + { + &TM3CNT, 0x10E, wxTRANSLATE("0x400010E-TM3CNT"), 0x00C7, + { + wxT(""), + wxTRANSLATE("Scalar Selection (2 bits)"), + wxTRANSLATE("Count Up"), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + &P1, 0x130, wxTRANSLATE("0x4000130-P1"), 0x03FF, + { + wxTRANSLATE("A"), + wxTRANSLATE("B"), + wxTRANSLATE("Select"), + wxTRANSLATE("Start"), + wxTRANSLATE("Right"), + wxTRANSLATE("Left"), + wxTRANSLATE("Up"), + wxTRANSLATE("Down"), + wxTRANSLATE("Shoulder Right"), + wxTRANSLATE("Shoulder Left"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x132, wxTRANSLATE("0x4000132-P1CNT"), 0xC3FF, + { + wxTRANSLATE("A"), + wxTRANSLATE("B"), + wxTRANSLATE("Select"), + wxTRANSLATE("Start"), + wxTRANSLATE("Right"), + wxTRANSLATE("Left"), + wxTRANSLATE("Up"), + wxTRANSLATE("Down"), + wxTRANSLATE("Shoulder Right"), + wxTRANSLATE("Shoulder Left"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Interrupt Request"), + wxTRANSLATE("Interrupt Condition"), + } + }, + { + &IE, 0x200, wxTRANSLATE("0x4000200-IE"), 0x3FFF, + { + wxTRANSLATE("VBlank"), + wxTRANSLATE("HBlank"), + wxTRANSLATE("VCount"), + wxTRANSLATE("Timer 0"), + wxTRANSLATE("Timer 1"), + wxTRANSLATE("Timer 2"), + wxTRANSLATE("Timer 3"), + wxTRANSLATE("Serial"), + wxTRANSLATE("DMA 0"), + wxTRANSLATE("DMA 1"), + wxTRANSLATE("DMA 2"), + wxTRANSLATE("DMA 3"), + wxTRANSLATE("Keypad"), + wxTRANSLATE("Game Pak"), + wxT(""), + wxT(""), + } + }, + { + &IF, 0x202, wxTRANSLATE("0x4000202-IF"), 0x0000, + { + wxTRANSLATE("VBlank"), + wxTRANSLATE("HBlank"), + wxTRANSLATE("VCount"), + wxTRANSLATE("Timer 0"), + wxTRANSLATE("Timer 1"), + wxTRANSLATE("Timer 2"), + wxTRANSLATE("Timer 3"), + wxTRANSLATE("Serial"), + wxTRANSLATE("DMA 0"), + wxTRANSLATE("DMA 1"), + wxTRANSLATE("DMA 2"), + wxTRANSLATE("DMA 3"), + wxTRANSLATE("Keypad"), + wxTRANSLATE("Game Pak"), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x204, wxTRANSLATE("0x4000204-WAITCNT"), 0x5FFF, + { + wxT(""), + wxTRANSLATE("SRAM Wait Control (2 bits)"), + wxT(""), + wxTRANSLATE("Wait State 0 First Access (2 bits)"), + wxTRANSLATE("Wait State 0 Second Access"), + wxT(""), + wxTRANSLATE("Wait State 1 First Access (2 bits)"), + wxTRANSLATE("Wait State 1 Second Access"), + wxT(""), + wxTRANSLATE("Wait State 2 First Access (2 bits)"), + wxTRANSLATE("Wait State 2 Second Access"), + wxT(""), + wxTRANSLATE("PHI Terminal Output (2 bits)"), + wxT(""), + wxTRANSLATE("Game Pak Prefetch Buffer"), + wxTRANSLATE("Game Pak Type Flag"), + } + }, + { + &IME, 0x208, wxTRANSLATE("0x4000208-IME"), 0x0001, + { + wxTRANSLATE("Master Interrupt Enable"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + } + }, + { + NULL, 0x300, wxTRANSLATE("0x4000300-HALTCNT"), 0x8001, + { + wxTRANSLATE("First Boot"), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxT(""), + wxTRANSLATE("Power Down"), + } + }, +}; + +#define NUM_IOREGS (sizeof(ioregs)/sizeof(ioregs[0])) diff --git a/src/wx/openal.cpp b/src/wx/openal.cpp new file mode 100644 index 00000000..092c7032 --- /dev/null +++ b/src/wx/openal.cpp @@ -0,0 +1,490 @@ +// === LOGALL writes very detailed informations to vba-trace.log === +//#define LOGALL + +#ifndef NO_OAL + +// for gopts +// also, wx-related +#include "wxvbam.h" + +// Interface +#include "../common/SoundDriver.h" + +// OpenAL +#include "openal.h" + +// Internals +#include "../gba/Sound.h" +#include "../gba/Globals.h" // for 'speedup' and 'synchronize' + +// Debug +#include +#define ASSERT_SUCCESS assert( AL_NO_ERROR == ALFunction.alGetError() ) + +#ifndef LOGALL +// replace logging functions with comments +#ifdef winlog +#undef winlog +#endif +#define winlog // +#define debugState() // +#endif + +struct OPENALFNTABLE; + +class OpenAL : public SoundDriver +{ +public: + OpenAL(); + virtual ~OpenAL(); + + static wxDynamicLibrary Lib; + static bool LoadOAL(); + static bool GetDevices(wxArrayString &names, wxArrayString &ids); + bool init(long sampleRate); // initialize the sound buffer queue + void pause(); // pause the secondary sound buffer + void reset(); // stop and reset the secondary sound buffer + void resume(); // play/resume the secondary sound buffer + void write(u16 * finalWave, int length); // write the emulated sound to a sound buffer + +private: + static OPENALFNTABLE ALFunction; + bool initialized; + bool buffersLoaded; + ALCdevice *device; + ALCcontext *context; + ALuint *buffer; + ALuint tempBuffer; + ALuint source; + int freq; + int soundBufferLen; + +#ifdef LOGALL + void debugState(); +#endif +}; + +OpenAL::OpenAL() +{ + initialized = false; + buffersLoaded = false; + device = NULL; + context = NULL; + buffer = (ALuint*)malloc( gopts.audio_buffers * sizeof( ALuint ) ); + memset( buffer, 0, gopts.audio_buffers * sizeof( ALuint ) ); + tempBuffer = 0; + source = 0; +} + + +OpenAL::~OpenAL() +{ + if( !initialized ) return; + + ALFunction.alSourceStop( source ); + ASSERT_SUCCESS; + + ALFunction.alSourcei( source, AL_BUFFER, 0 ); + ASSERT_SUCCESS; + + ALFunction.alDeleteSources( 1, &source ); + ASSERT_SUCCESS; + + ALFunction.alDeleteBuffers( gopts.audio_buffers, buffer ); + ASSERT_SUCCESS; + + free( buffer ); + + ALFunction.alcMakeContextCurrent( NULL ); + // Wine incorrectly returns ALC_INVALID_VALUE + // and then fails the rest of these functions as well + // so there will be a leak under Wine, but that's a bug in Wine, not + // this code + //ASSERT_SUCCESS; + + ALFunction.alcDestroyContext( context ); + //ASSERT_SUCCESS; + + ALFunction.alcCloseDevice( device ); + //ASSERT_SUCCESS; + ALFunction.alGetError(); // reset error state +} + +#ifdef LOGALL +void OpenAL::debugState() +{ + + ALint value = 0; + ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &value ); + ASSERT_SUCCESS; + + winlog( " soundPaused = %i\n", soundPaused ); + winlog( " Source:\n" ); + winlog( " State: " ); + switch( value ) + { + case AL_INITIAL: + winlog( "AL_INITIAL\n" ); + break; + case AL_PLAYING: + winlog( "AL_PLAYING\n" ); + break; + case AL_PAUSED: + winlog( "AL_PAUSED\n" ); + break; + case AL_STOPPED: + winlog( "AL_STOPPED\n" ); + break; + default: + winlog( "!unknown!\n" ); + break; + } + + + ALFunction.alGetSourcei( source, AL_BUFFERS_QUEUED, &value ); + ASSERT_SUCCESS; + winlog( " Buffers in queue: %i\n", value ); + + ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &value ); + ASSERT_SUCCESS; + winlog( " Buffers processed: %i\n", value ); +} +#endif + + +bool OpenAL::init(long sampleRate) +{ + winlog( "OpenAL::init\n" ); + assert( initialized == false ); + + if( !LoadOAL() ) { + wxLogError( _("OpenAL library could not be found on your system. Please install the runtime from http://openal.org") ); + return false; + } + + if( !gopts.audio_dev.empty() ) { + device = ALFunction.alcOpenDevice( gopts.audio_dev.mb_str() ); + } else { + device = ALFunction.alcOpenDevice( NULL ); + } + assert( device != NULL ); + + context = ALFunction.alcCreateContext( device, NULL ); + assert( context != NULL ); + + ALCboolean retVal = ALFunction.alcMakeContextCurrent( context ); + assert( ALC_TRUE == retVal ); + + ALFunction.alGenBuffers( gopts.audio_buffers, buffer ); + ASSERT_SUCCESS; + + ALFunction.alGenSources( 1, &source ); + ASSERT_SUCCESS; + + freq = sampleRate; + + // calculate the number of samples per frame first + // then multiply it with the size of a sample frame (16 bit * stereo) + soundBufferLen = ( freq / 60 ) * 4; + + + initialized = true; + return true; +} + + +void OpenAL::resume() +{ + if( !initialized ) return; + winlog( "OpenAL::resume\n" ); + if( !buffersLoaded ) return; + debugState(); + + + ALint sourceState = 0; + ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); + ASSERT_SUCCESS; + if( sourceState != AL_PLAYING ) { + ALFunction.alSourcePlay( source ); + ASSERT_SUCCESS; + } + debugState(); +} + + +void OpenAL::pause() +{ + if( !initialized ) return; + winlog( "OpenAL::pause\n" ); + if( !buffersLoaded ) return; + debugState(); + + + ALint sourceState = 0; + ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); + ASSERT_SUCCESS; + if( sourceState == AL_PLAYING ) { + ALFunction.alSourcePause( source ); + ASSERT_SUCCESS; + } + debugState(); +} + + +void OpenAL::reset() +{ + if( !initialized ) return; + winlog( "OpenAL::reset\n" ); + if( !buffersLoaded ) return; + debugState(); + + ALint sourceState = 0; + ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); + ASSERT_SUCCESS; + if( sourceState != AL_STOPPED ) { + ALFunction.alSourceStop( source ); + ASSERT_SUCCESS; + } + debugState(); +} + + +void OpenAL::write(u16 * finalWave, int length) +{ + if( !initialized ) return; + winlog( "OpenAL::write\n" ); + + debugState(); + + ALint sourceState = 0; + ALint nBuffersProcessed = 0; + + if( !buffersLoaded ) { + // ==initial buffer filling== + winlog( " initial buffer filling\n" ); + for( int i = 0 ; i < gopts.audio_buffers ; i++ ) { + // Filling the buffers explicitly with silence would be cleaner, + // but the very first sample is usually silence anyway. + ALFunction.alBufferData( buffer[i], AL_FORMAT_STEREO16, finalWave, soundBufferLen, freq ); + ASSERT_SUCCESS; + } + + ALFunction.alSourceQueueBuffers( source, gopts.audio_buffers, buffer ); + ASSERT_SUCCESS; + + buffersLoaded = true; + } else { + // ==normal buffer refreshing== + nBuffersProcessed = 0; + ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed ); + ASSERT_SUCCESS; + + if( nBuffersProcessed == gopts.audio_buffers ) { + // we only want to know about it when we are emulating at full speed or faster: + if( ( gopts.throttle >= 100 ) || ( gopts.throttle == 0 ) ) { + if( systemVerbose & VERBOSE_SOUNDOUTPUT ) { + static unsigned int i = 0; + log( "OpenAL: Buffers were not refilled fast enough (i=%i)\n", i++ ); + } + } + } + + if( !speedup && synchronize && !gopts.throttle ) { + // wait until at least one buffer has finished + while( nBuffersProcessed == 0 ) { + winlog( " waiting...\n" ); + // wait for about half the time one buffer needs to finish + // unoptimized: ( sourceBufferLen * 1000 ) / ( freq * 2 * 2 ) * 1/2 + wxMilliSleep( soundBufferLen / ( freq >> 7 ) ); + ALFunction.alGetSourcei( source, AL_BUFFERS_PROCESSED, &nBuffersProcessed ); + ASSERT_SUCCESS; + } + } else { + if( nBuffersProcessed == 0 ) return; + } + + assert( nBuffersProcessed > 0 ); + + // unqueue buffer + tempBuffer = 0; + ALFunction.alSourceUnqueueBuffers( source, 1, &tempBuffer ); + ASSERT_SUCCESS; + + // refill buffer + ALFunction.alBufferData( tempBuffer, AL_FORMAT_STEREO16, finalWave, soundBufferLen, freq ); + ASSERT_SUCCESS; + + // requeue buffer + ALFunction.alSourceQueueBuffers( source, 1, &tempBuffer ); + ASSERT_SUCCESS; + } + + // start playing the source if necessary + ALFunction.alGetSourcei( source, AL_SOURCE_STATE, &sourceState ); + ASSERT_SUCCESS; + if( !soundPaused && ( sourceState != AL_PLAYING ) ) { + ALFunction.alSourcePlay( source ); + ASSERT_SUCCESS; + } +} + +SoundDriver *newOpenAL() +{ + winlog( "newOpenAL\n" ); + return new OpenAL(); +} + +// no more use of copyrighted OpenAL code just to load the stupid library +// this is for compatibility with MFC version +// positive: make an OpenAL-capable binary which does not require OpenAL +// negative: openal lib may not be in library path +// openal lib name is OS-dependent, and may even change based +// on where it was installed from +// On UNIX, it would probably be better to just hard link with libopenal + +OPENALFNTABLE OpenAL::ALFunction = {NULL}; +wxDynamicLibrary OpenAL::Lib; + +bool OpenAL::LoadOAL() +{ + if(!Lib.IsLoaded() && +#ifdef __WXMSW__ + // on win32, it's openal32.dll + !Lib.Load(wxT("openal32")) && +#else +#ifdef __WXMAC__ + // on macosx, it's just plain OpenAL + !Lib.Load(wxT("OpenAL"), wxDL_NOW|wxDL_VERBATIM) && +#endif +#endif + // on linux, it's libopenal.so + // try standard name on all platforms + !Lib.Load(wxDynamicLibrary::CanonicalizeName(wxT("openal")))) + return false; +#define loadfn(t, n) do { \ + if(!(ALFunction.n = (t)Lib.GetSymbol(wxT(#n)))) \ + return false; \ +} while(0) + //loadfn(LPALENABLE, alEnable); + //loadfn(LPALDISABLE, alDisable); + //loadfn(LPALISENABLED, alIsEnabled); + //loadfn(LPALGETSTRING, alGetString); + //loadfn(LPALGETBOOLEANV, alGetBooleanv); + //loadfn(LPALGETINTEGERV, alGetIntegerv); + //loadfn(LPALGETFLOATV, alGetFloatv); + //loadfn(LPALGETDOUBLEV, alGetDoublev); + //loadfn(LPALGETBOOLEAN, alGetBoolean); + //loadfn(LPALGETINTEGER, alGetInteger); + //loadfn(LPALGETFLOAT, alGetFloat); + //loadfn(LPALGETDOUBLE, alGetDouble); + loadfn(LPALGETERROR, alGetError); + //loadfn(LPALISEXTENSIONPRESENT, alIsExtensionPresent); + //loadfn(LPALGETPROCADDRESS, alGetProcAddress); + //loadfn(LPALGETENUMVALUE, alGetEnumValue); + //loadfn(LPALLISTENERF, alListenerf); + //loadfn(LPALLISTENER3F, alListener3f); + //loadfn(LPALLISTENERFV, alListenerfv); + //loadfn(LPALLISTENERI, alListeneri); + //loadfn(LPALLISTENER3I, alListener3i); + //loadfn(LPALLISTENERIV, alListeneriv); + //loadfn(LPALGETLISTENERF, alGetListenerf); + //loadfn(LPALGETLISTENER3F, alGetListener3f); + //loadfn(LPALGETLISTENERFV, alGetListenerfv); + //loadfn(LPALGETLISTENERI, alGetListeneri); + //loadfn(LPALGETLISTENER3I, alGetListener3i); + //loadfn(LPALGETLISTENERIV, alGetListeneriv); + loadfn(LPALGENSOURCES, alGenSources); + loadfn(LPALDELETESOURCES, alDeleteSources); + //loadfn(LPALISSOURCE, alIsSource); + //loadfn(LPALSOURCEF, alSourcef); + //loadfn(LPALSOURCE3F, alSource3f); + //loadfn(LPALSOURCEFV, alSourcefv); + loadfn(LPALSOURCEI, alSourcei); + //loadfn(LPALSOURCE3I, alSource3i); + //loadfn(LPALSOURCEIV, alSourceiv); + //loadfn(LPALGETSOURCEF, alGetSourcef); + //loadfn(LPALGETSOURCE3F, alGetSource3f); + //loadfn(LPALGETSOURCEFV, alGetSourcefv); + loadfn(LPALGETSOURCEI, alGetSourcei); + //loadfn(LPALGETSOURCE3I, alGetSource3i); + //loadfn(LPALGETSOURCEIV, alGetSourceiv); + //loadfn(LPALSOURCEPLAYV, alSourcePlayv); + //loadfn(LPALSOURCESTOPV, alSourceStopv); + //loadfn(LPALSOURCEREWINDV, alSourceRewindv); + //loadfn(LPALSOURCEPAUSEV, alSourcePausev); + loadfn(LPALSOURCEPLAY, alSourcePlay); + loadfn(LPALSOURCESTOP, alSourceStop); + //loadfn(LPALSOURCEREWIND, alSourceRewind); + loadfn(LPALSOURCEPAUSE, alSourcePause); + loadfn(LPALSOURCEQUEUEBUFFERS, alSourceQueueBuffers); + loadfn(LPALSOURCEUNQUEUEBUFFERS, alSourceUnqueueBuffers); + loadfn(LPALGENBUFFERS, alGenBuffers); + loadfn(LPALDELETEBUFFERS, alDeleteBuffers); + //loadfn(LPALISBUFFER, alIsBuffer); + loadfn(LPALBUFFERDATA, alBufferData); + //loadfn(LPALBUFFERF, alBufferf); + //loadfn(LPALBUFFER3F, alBuffer3f); + //loadfn(LPALBUFFERFV, alBufferfv); + //loadfn(LPALBUFFERI, alBufferi); + //loadfn(LPALBUFFER3I, alBuffer3i); + //loadfn(LPALBUFFERIV, alBufferiv); + //loadfn(LPALGETBUFFERF, alGetBufferf); + //loadfn(LPALGETBUFFER3F, alGetBuffer3f); + //loadfn(LPALGETBUFFERFV, alGetBufferfv); + //loadfn(LPALGETBUFFERI, alGetBufferi); + //loadfn(LPALGETBUFFER3I, alGetBuffer3i); + //loadfn(LPALGETBUFFERIV, alGetBufferiv); + //loadfn(LPALDOPPLERFACTOR, alDopplerFactor); + //loadfn(LPALDOPPLERVELOCITY, alDopplerVelocity); + //loadfn(LPALSPEEDOFSOUND, alSpeedOfSound); + //loadfn(LPALDISTANCEMODEL, alDistanceModel); + + loadfn(LPALCCREATECONTEXT, alcCreateContext); + loadfn(LPALCMAKECONTEXTCURRENT, alcMakeContextCurrent); + //loadfn(LPALCPROCESSCONTEXT, alcProcessContext); + //loadfn(LPALCSUSPENDCONTEXT, alcSuspendContext); + loadfn(LPALCDESTROYCONTEXT, alcDestroyContext); + //loadfn(LPALCGETCURRENTCONTEXT, alcGetCurrentContext); + //loadfn(LPALCGETCONTEXTSDEVICE, alcGetContextsDevice); + loadfn(LPALCOPENDEVICE, alcOpenDevice); + loadfn(LPALCCLOSEDEVICE, alcCloseDevice); + //loadfn(LPALCGETERROR, alcGetError); + loadfn(LPALCISEXTENSIONPRESENT, alcIsExtensionPresent); + //loadfn(LPALCGETPROCADDRESS, alcGetProcAddress); + //loadfn(LPALCGETENUMVALUE, alcGetEnumValue); + loadfn(LPALCGETSTRING, alcGetString); + //loadfn(LPALCGETINTEGERV, alcGetIntegerv); + //loadfn(LPALCCAPTUREOPENDEVICE, alcCaptureOpenDevice); + //loadfn(LPALCCAPTURECLOSEDEVICE, alcCaptureCloseDevice); + //loadfn(LPALCCAPTURESTART, alcCaptureStart); + //loadfn(LPALCCAPTURESTOP, alcCaptureStop); + //loadfn(LPALCCAPTURESAMPLES, alcCaptureSamples); + return true; +} + +bool GetOALDevices(wxArrayString &names, wxArrayString &ids) +{ + return OpenAL::GetDevices(names, ids); +} + +bool OpenAL::GetDevices(wxArrayString &names, wxArrayString &ids) +{ + if(!OpenAL::LoadOAL()) + return false; +#ifdef ALC_DEVICE_SPECIFIER + if(ALFunction.alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT") == AL_FALSE) + // this extension isn't critical to OpenAL operating + return true; + const char *devs = ALFunction.alcGetString(NULL, ALC_DEVICE_SPECIFIER); + while(*devs) { + names.push_back(wxString(devs, wxConvLibc)); + ids.push_back(names[names.size() - 1]); + devs += strlen(devs) + 1; + } +#else + // should work anyway, but must always use default driver + return true; +#endif +} + +#endif diff --git a/src/wx/openal.h b/src/wx/openal.h new file mode 100644 index 00000000..a0a2abac --- /dev/null +++ b/src/wx/openal.h @@ -0,0 +1,123 @@ +// on win32 and mac, pointer typedefs only happen with AL_NO_PROTOTYPES +// on mac, ALC_NO_PROTOTYPES as well + +#define AL_NO_PROTOTYPES 1 + +// on mac, alc pointer typedefs ony happen for ALC if ALC_NO_PROTOTYPES +// unfortunately, there is a bug in the system headers (use of ALCvoid when +// void should be used; shame on Apple for introducing this error, and shame +// on Creative for making a typedef to void in the first place) +//#define ALC_NO_PROTOTYPES 1 + +#include +#include + +// since the ALC typedefs are broken on Mac: + +#ifdef __WXMAC__ +typedef ALCcontext * (ALC_APIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist); +typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context ); +typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context ); +typedef ALCdevice * (ALC_APIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename ); +typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device ); +typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname ); +typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param ); +#endif + +// no more use of copyrighted OpenAL code just to load the stupid library +struct OPENALFNTABLE { + //LPALENABLE alEnable; + //LPALDISABLE alDisable; + //LPALISENABLED alIsEnabled; + //LPALGETSTRING alGetString; + //LPALGETBOOLEANV alGetBooleanv; + //LPALGETINTEGERV alGetIntegerv; + //LPALGETFLOATV alGetFloatv; + //LPALGETDOUBLEV alGetDoublev; + //LPALGETBOOLEAN alGetBoolean; + //LPALGETINTEGER alGetInteger; + //LPALGETFLOAT alGetFloat; + //LPALGETDOUBLE alGetDouble; + LPALGETERROR alGetError; + LPALISEXTENSIONPRESENT alIsExtensionPresent; + //LPALGETPROCADDRESS alGetProcAddress; + //LPALGETENUMVALUE alGetEnumValue; + //LPALLISTENERF alListenerf; + //LPALLISTENER3F alListener3f; + //LPALLISTENERFV alListenerfv; + //LPALLISTENERI alListeneri; + //LPALLISTENER3I alListener3i; + //LPALLISTENERIV alListeneriv; + //LPALGETLISTENERF alGetListenerf; + //LPALGETLISTENER3F alGetListener3f; + //LPALGETLISTENERFV alGetListenerfv; + //LPALGETLISTENERI alGetListeneri; + //LPALGETLISTENER3I alGetListener3i; + //LPALGETLISTENERIV alGetListeneriv; + LPALGENSOURCES alGenSources; + LPALDELETESOURCES alDeleteSources; + //LPALISSOURCE alIsSource; + //LPALSOURCEF alSourcef; + //LPALSOURCE3F alSource3f; + //LPALSOURCEFV alSourcefv; + LPALSOURCEI alSourcei; + //LPALSOURCE3I alSource3i; + //LPALSOURCEIV alSourceiv; + //LPALGETSOURCEF alGetSourcef; + //LPALGETSOURCE3F alGetSource3f; + //LPALGETSOURCEFV alGetSourcefv; + LPALGETSOURCEI alGetSourcei; + //LPALGETSOURCE3I alGetSource3i; + //LPALGETSOURCEIV alGetSourceiv; + //LPALSOURCEPLAYV alSourcePlayv; + //LPALSOURCESTOPV alSourceStopv; + //LPALSOURCEREWINDV alSourceRewindv; + //LPALSOURCEPAUSEV alSourcePausev; + LPALSOURCEPLAY alSourcePlay; + LPALSOURCESTOP alSourceStop; + //LPALSOURCEREWIND alSourceRewind; + LPALSOURCEPAUSE alSourcePause; + LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers; + LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers; + LPALGENBUFFERS alGenBuffers; + LPALDELETEBUFFERS alDeleteBuffers; + //LPALISBUFFER alIsBuffer; + LPALBUFFERDATA alBufferData; + //LPALBUFFERF alBufferf; + //LPALBUFFER3F alBuffer3f; + //LPALBUFFERFV alBufferfv; + //LPALBUFFERI alBufferi; + //LPALBUFFER3I alBuffer3i; + //LPALBUFFERIV alBufferiv; + //LPALGETBUFFERF alGetBufferf; + //LPALGETBUFFER3F alGetBuffer3f; + //LPALGETBUFFERFV alGetBufferfv; + //LPALGETBUFFERI alGetBufferi; + //LPALGETBUFFER3I alGetBuffer3i; + //LPALGETBUFFERIV alGetBufferiv; + //LPALDOPPLERFACTOR alDopplerFactor; + //LPALDOPPLERVELOCITY alDopplerVelocity; + //LPALSPEEDOFSOUND alSpeedOfSound; + //LPALDISTANCEMODEL alDistanceModel; + + LPALCCREATECONTEXT alcCreateContext; + LPALCMAKECONTEXTCURRENT alcMakeContextCurrent; + //LPALCPROCESSCONTEXT alcProcessContext; + //LPALCSUSPENDCONTEXT alcSuspendContext; + LPALCDESTROYCONTEXT alcDestroyContext; + //LPALCGETCURRENTCONTEXT alcGetCurrentContext; + //LPALCGETCONTEXTSDEVICE alcGetContextsDevice; + LPALCOPENDEVICE alcOpenDevice; + LPALCCLOSEDEVICE alcCloseDevice; + //LPALCGETERROR alcGetError; + LPALCISEXTENSIONPRESENT alcIsExtensionPresent; + //LPALCGETPROCADDRESS alcGetProcAddress; + //LPALCGETENUMVALUE alcGetEnumValue; + LPALCGETSTRING alcGetString; + //LPALCGETINTEGERV alcGetIntegerv; + //LPALCCAPTUREOPENDEVICE alcCaptureOpenDevice; + //LPALCCAPTURECLOSEDEVICE alcCaptureCloseDevice; + //LPALCCAPTURESTART alcCaptureStart; + //LPALCCAPTURESTOP alcCaptureStop; + //LPALCCAPTURESAMPLES alcCaptureSamples; +}; diff --git a/src/wx/opts.cpp b/src/wx/opts.cpp new file mode 100644 index 00000000..2e7e872e --- /dev/null +++ b/src/wx/opts.cpp @@ -0,0 +1,773 @@ +#include "wxvbam.h" +#include +#include +/* + * disableSfx(F) -> cpuDisableSfx + * priority(2) -> threadPriority + * saveMoreCPU(F) -> Sm60FPS + * + * SDL: + * -p/--profile=hz + */ + +/* not sure how well other compilers support field-init syntax */ +#define STROPT(n, d, v) {wxT(n), d, &v} +#define INTOPT(n, d, v, max, min) {wxT(n), d, NULL, &v, NULL, max, min} +#define BOOLOPT(n, d, v) {wxT(n), d, NULL, NULL, NULL, 0, 0, &v} +#define ENUMOPT(n, d, v, e) {wxT(n), d, NULL, &v, e} + +opts_t gopts; + +// having the standard menu accels here means they will work even without menus +const wxAcceleratorEntry default_accels[] = { + wxAcceleratorEntry(wxMOD_CMD, wxT('C'), XRCID("CheatsList")), + wxAcceleratorEntry(wxMOD_CMD, wxT('N'), XRCID("NextFrame")), + // some ports add ctrl-q anyway, so may as well make it official + // maybe make alt-f4 universal as well... + // FIXME: ctrl-Q does not work on wxMSW + // FIXME: esc does not work on wxMSW + wxAcceleratorEntry(wxMOD_NONE, WXK_ESCAPE, wxID_EXIT), + wxAcceleratorEntry(wxMOD_CMD, wxT('X'), wxID_EXIT), + wxAcceleratorEntry(wxMOD_CMD, wxT('Q'), wxID_EXIT), + // FIXME: ctrl-W does not work on wxMSW + wxAcceleratorEntry(wxMOD_CMD, wxT('W'), wxID_CLOSE), + // load most recent is more commonly used than load other + //wxAcceleratorEntry(wxMOD_CMD, wxT('L'), XRCID("Load")), + wxAcceleratorEntry(wxMOD_CMD, wxT('L'), XRCID("LoadGameRecent")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F1, XRCID("LoadGame01")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F2, XRCID("LoadGame02")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F3, XRCID("LoadGame03")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F4, XRCID("LoadGame04")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F5, XRCID("LoadGame05")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F6, XRCID("LoadGame06")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F7, XRCID("LoadGame07")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F8, XRCID("LoadGame08")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F9, XRCID("LoadGame09")), + wxAcceleratorEntry(wxMOD_NONE, WXK_F10, XRCID("LoadGame10")), + wxAcceleratorEntry(wxMOD_NONE, WXK_PAUSE, XRCID("Pause")), + wxAcceleratorEntry(wxMOD_CMD, wxT('P'), XRCID("Pause")), + wxAcceleratorEntry(wxMOD_CMD, wxT('R'), XRCID("Reset")), + // save oldest is more commonly used than save other + //wxAcceleratorEntry(wxMOD_CMD, wxT('S'), XRCID("Save")), + wxAcceleratorEntry(wxMOD_CMD, wxT('S'), XRCID("SaveGameOldest")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F1, XRCID("SaveGame01")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F2, XRCID("SaveGame02")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F3, XRCID("SaveGame03")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F4, XRCID("SaveGame04")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F5, XRCID("SaveGame05")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F6, XRCID("SaveGame06")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F7, XRCID("SaveGame07")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F8, XRCID("SaveGame08")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F9, XRCID("SaveGame09")), + wxAcceleratorEntry(wxMOD_SHIFT, WXK_F10, XRCID("SaveGame10")), + // I prefer the SDL ESC key binding + //wxAcceleratorEntry(wxMOD_NONE, WXK_ESCAPE, XRCID("ToggleFullscreen"), + // alt-enter is more standard anyway + wxAcceleratorEntry(wxMOD_ALT, WXK_RETURN, XRCID("ToggleFullscreen")), + wxAcceleratorEntry(wxMOD_ALT, wxT('1'), XRCID("JoypadAutofireA")), + wxAcceleratorEntry(wxMOD_ALT, wxT('2'), XRCID("JoypadAutofireB")), + wxAcceleratorEntry(wxMOD_ALT, wxT('3'), XRCID("JoypadAutofireL")), + wxAcceleratorEntry(wxMOD_ALT, wxT('4'), XRCID("JoypadAutofireR")), + wxAcceleratorEntry(wxMOD_CMD, wxT('1'), XRCID("VideoLayersBG0")), + wxAcceleratorEntry(wxMOD_CMD, wxT('2'), XRCID("VideoLayersBG1")), + wxAcceleratorEntry(wxMOD_CMD, wxT('3'), XRCID("VideoLayersBG2")), + wxAcceleratorEntry(wxMOD_CMD, wxT('4'), XRCID("VideoLayersBG3")), + wxAcceleratorEntry(wxMOD_CMD, wxT('5'), XRCID("VideoLayersOBJ")), + wxAcceleratorEntry(wxMOD_CMD, wxT('6'), XRCID("VideoLayersWIN0")), + wxAcceleratorEntry(wxMOD_CMD, wxT('7'), XRCID("VideoLayersWIN1")), + wxAcceleratorEntry(wxMOD_CMD, wxT('8'), XRCID("VideoLayersOBJWIN")), + wxAcceleratorEntry(wxMOD_CMD, wxT('B'), XRCID("Rewind")), + // following are not in standard menus + // FILExx are filled in when recent menu is filled + wxAcceleratorEntry(wxMOD_CMD, WXK_F1, wxID_FILE1), + wxAcceleratorEntry(wxMOD_CMD, WXK_F2, wxID_FILE2), + wxAcceleratorEntry(wxMOD_CMD, WXK_F3, wxID_FILE3), + wxAcceleratorEntry(wxMOD_CMD, WXK_F4, wxID_FILE4), + wxAcceleratorEntry(wxMOD_CMD, WXK_F5, wxID_FILE5), + wxAcceleratorEntry(wxMOD_CMD, WXK_F6, wxID_FILE6), + wxAcceleratorEntry(wxMOD_CMD, WXK_F7, wxID_FILE7), + wxAcceleratorEntry(wxMOD_CMD, WXK_F8, wxID_FILE8), + wxAcceleratorEntry(wxMOD_CMD, WXK_F9, wxID_FILE9), + wxAcceleratorEntry(wxMOD_CMD, WXK_F10, wxID_FILE10), + wxAcceleratorEntry(wxMOD_CMD, wxT('0'), XRCID("VideoLayersReset")), + wxAcceleratorEntry(wxMOD_CMD, wxT('G'), XRCID("ChangeFilter")), + wxAcceleratorEntry(wxMOD_NONE, WXK_NUMPAD_ADD, XRCID("IncreaseVolume")), + wxAcceleratorEntry(wxMOD_NONE, WXK_NUMPAD_SUBTRACT, XRCID("DecreaseVolume")), + wxAcceleratorEntry(wxMOD_NONE, WXK_NUMPAD_ENTER, XRCID("ToggleSound")) +}; +const int num_def_accels = sizeof(default_accels)/sizeof(default_accels[0]); + +// Note: this must match GUI widget names or GUI won't work +// This table's order determines tab order as well +const wxChar * const joynames[NUM_KEYS] = { + wxT("Up"), wxT("Down"), wxT("Left"), wxT("Right"), + wxT("A"), wxT("B"), wxT("L"), wxT("R"), + wxT("Select"), wxT("Start"), + wxT("MotionUp"), wxT("MotionDown"), wxT("MotionLeft"), wxT("MotionRight"), + wxT("AutoA"), wxT("AutoB"), + wxT("Speed"), wxT("Capture"), wxT("GS") +}; + +wxJoyKeyBinding defkeys[NUM_KEYS * 2] = { + { WXK_UP }, { 1, WXJB_AXIS_MINUS, 1 }, { WXK_DOWN }, { 1, WXJB_AXIS_PLUS, 1 }, + { WXK_LEFT }, { 0, WXJB_AXIS_MINUS, 1 }, { WXK_RIGHT }, { 0, WXJB_AXIS_PLUS, 1 }, + { wxT('X') }, { 0, WXJB_BUTTON, 1 }, { wxT('Z') }, { 1, WXJB_BUTTON, 1 }, + { wxT('A') }, { 2, WXJB_BUTTON, 1 }, { wxT('S') }, { 3, WXJB_BUTTON, 1 }, + { WXK_BACK }, { 4, WXJB_BUTTON, 1 }, { WXK_RETURN }, { 5, WXJB_BUTTON, 1 }, + { WXK_NUMPAD_UP }, { 2, WXJB_AXIS_PLUS, 1 }, { WXK_NUMPAD_DOWN }, { 2, WXJB_AXIS_MINUS, 1 }, + { WXK_NUMPAD_LEFT }, { 3, WXJB_AXIS_MINUS, 1 }, { WXK_NUMPAD_RIGHT }, { 3, WXJB_AXIS_PLUS, 1 }, + { wxT('W') }, { 0 }, { wxT('Q') }, { 0 }, + { WXK_SPACE }, { 0 }, { WXK_F11 }, { 0 }, + { 0 } , { 0 } +}; + +wxAcceleratorEntry_v sys_accels; + +// Note: this table must be sorted in option name order +// Both for better user display and for (fast) searching by name +opt_desc opts[] = { + BOOLOPT("Display/Bilinear", wxTRANSLATE("Use bilinear filter with 3d renderer"), gopts.bilinear), + BOOLOPT("Display/DisableStatus", wxTRANSLATE("Disable on-screen status messages"), gopts.no_osd_status), +#ifdef MMX + BOOLOPT("Display/EnableMMX", wxTRANSLATE("Enable MMX"), cpu_mmx), +#endif + ENUMOPT("Display/Filter", wxTRANSLATE("Full-screen filter to apply"), gopts.filter, + wxTRANSLATE("none|2xsai|super2xsai|supereagle|pixelate|advmame|" + "bilinear|bilinearplus|scanlines|tvmode|hq2x|lq2x|" + "simple2x|simple3x|hq3x|simple4x|hq4x|plugin")), + STROPT ("Display/FilterPlugin", wxTRANSLATE("Filter plugin library"), gopts.filter_plugin), + BOOLOPT("Display/Fullscreen", wxTRANSLATE("Enter fullscreen mode at startup"), gopts.fullscreen), + INTOPT ("Display/FullscreenDepth", wxTRANSLATE("Fullscreen mode color depth (0 = any)"), gopts.fs_mode.bpp, 0, 999), + INTOPT ("Display/FullscreenFreq", wxTRANSLATE("Fullscreen mode frequency (0 = any)"), gopts.fs_mode.refresh, 0, 999), + INTOPT ("Display/FullscreenHeight", wxTRANSLATE("Fullscreen mode height (0 = desktop)"), gopts.fs_mode.h, 0, 99999), + INTOPT ("Display/FullscreenWidth", wxTRANSLATE("Fullscreen mode width (0 = desktop)"), gopts.fs_mode.w, 0, 99999), + ENUMOPT("Display/IFB", wxTRANSLATE("Interframe blending function"), gopts.ifb, wxTRANSLATE("none|smart|motionblur")), + INTOPT ("Display/MaxScale", wxTRANSLATE("Maximum scale factor (0 = no limit)"), gopts.max_scale, 0, 100), + INTOPT ("Display/MaxThreads", wxTRANSLATE("Maximum number of threads to run filters in"), gopts.max_threads, 1, 8), + ENUMOPT("Display/RenderMethod", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, +#ifdef __WXMSW__ + // try to keep variations to a minimum to ease translation + // since the config file stores strings rather than numbers, the + // ordering does not have to stay fixed + // but the numbers need to match the usage in code + wxTRANSLATE("simple|opengl|cairo|direct3d") +#else + wxTRANSLATE("simple|opengl|cairo") +#endif + ), + INTOPT ("Display/Scale", wxTRANSLATE("Default scale factor"), gopts.video_scale, 1, 6), + ENUMOPT("Display/ShowSpeed", wxTRANSLATE("Show speed indicator"), gopts.osd_speed, wxTRANSLATE("no|percent|detailed")), + BOOLOPT("Display/Stretch", wxTRANSLATE("Retain aspect ratio when resizing"), gopts.retain_aspect), + BOOLOPT("Display/Transparent", wxTRANSLATE("Draw on-screen messages transparently"), gopts.osd_transparent), + BOOLOPT("Display/Vsync", wxTRANSLATE("Wait for vertical sync"), gopts.vsync), + BOOLOPT("GB/AutomaticBorder", wxTRANSLATE("Automatically enable border for Super GameBoy games"), gbBorderAutomatic), + STROPT ("GB/BiosFile", wxTRANSLATE("BIOS file to use for GB, if enabled"), gopts.gb_bios), + BOOLOPT("GB/Border", wxTRANSLATE("Always enable border"), gbBorderOn), + ENUMOPT("GB/EmulatorType", wxTRANSLATE("Type of system to emulate"), gbEmulatorType, wxTRANSLATE("auto|gba|gbc|sgb|sgb2|gb")), + BOOLOPT("GB/EnablePrinter", wxTRANSLATE("Enable printer emulation"), gopts.gbprint), + INTOPT ("GB/FrameSkip", wxTRANSLATE("Skip frames. Values are 0-9 or -1 to skip automatically based on time."), gopts.gb_frameskip, -1, 9), + STROPT ("GB/GBCBiosFile", wxTRANSLATE("BIOS file to use for GBC, if enabled"), gopts.gbc_bios), + BOOLOPT("GB/GBCUseBiosFile", wxTRANSLATE("Use the specified BIOS file for GBC"), gopts.gbc_use_bios), + BOOLOPT("GB/LCDColor", wxTRANSLATE("Emulate washed colors of LCD"), gbColorOption), + ENUMOPT("GB/Palette", wxTRANSLATE("The palette to use"), gbPaletteOption, wxTRANSLATE("default|user1|user2")), + { wxT("GB/Palette0"), wxTRANSLATE("The default palette, as 8 comma-separated 4-digit hex integers (rgb555).") }, + { wxT("GB/Palette1"), wxTRANSLATE("The first user palette, as 8 comma-separated 4-digit hex integers (rgb555).") }, + { wxT("GB/Palette2"), wxTRANSLATE("The second user palette, as 8 comma-separated 4-digit hex integers (rgb555).") }, + BOOLOPT("GB/PrintAutoPage", wxTRANSLATE("Automatically gather a full page before printing"), gopts.print_auto_page), + BOOLOPT("GB/PrintScreenCap", wxTRANSLATE("Automatically save printouts as screen captures with -print suffix"), gopts.print_screen_cap), + STROPT ("GB/ROMDir", wxTRANSLATE("Directory to look for ROM files"), gopts.gb_rom_dir), + BOOLOPT("GB/UseBiosFile", wxTRANSLATE("Use the specified BIOS file for GB"), gopts.gb_use_bios), + BOOLOPT("GBA/AGBPrinter", wxTRANSLATE("Enable AGB printer"), gopts.agbprint), + STROPT ("GBA/BiosFile", wxTRANSLATE("BIOS file to use, if enabled"), gopts.gba_bios), + BOOLOPT("GBA/EnableRTC", wxTRANSLATE("Enable RTC (vba-over.ini override is rtcEnabled"), gopts.rtc), + ENUMOPT("GBA/FlashSize", wxTRANSLATE("Flash size (kb) (vba-over.ini override is flashSize in bytes)"), gopts.flash_size, wxTRANSLATE("64|128")), + INTOPT ("GBA/FrameSkip", wxTRANSLATE("Skip frames. Values are 0-9 or -1 to skip automatically based on time."), gopts.gba_frameskip, -1, 9), +#ifndef NO_LINK + BOOLOPT("GBA/Joybus", wxTRANSLATE("Enable joybus"), gba_joybus_enabled), + STROPT ("GBA/JoybusHost", wxTRANSLATE("Joybus host address"), gopts.joybus_host), + BOOLOPT("GBA/Link", wxTRANSLATE("Enable link cable"), gba_link_enabled), + BOOLOPT("GBA/LinkFast", wxTRANSLATE("Enable faster network protocol by default"), lanlink.speed), + STROPT ("GBA/LinkHost", wxTRANSLATE("Default network link client host"), gopts.link_host), + ENUMOPT("GBA/LinkProto", wxTRANSLATE("Default network protocol"), gopts.link_proto, wxTRANSLATE("tcp|udp")), + BOOLOPT("GBA/LinkRFU", wxTRANSLATE("Enable RFU for link"), rfu_enabled), + INTOPT ("GBA/LinkTimeout", wxTRANSLATE("Link timeout (ms)"), linktimeout, 0, 9999999), +#endif + STROPT ("GBA/ROMDir", wxTRANSLATE("Directory to look for ROM files"), gopts.gba_rom_dir), + ENUMOPT("GBA/SaveType", wxTRANSLATE("Native save (\"battery\") hardware type (vba-over.ini override is saveType integer 0-5)"), gopts.save_type, wxTRANSLATE("auto|eeprom|sram|flash|eeprom+sensor|none")), +#if 0 // currently disabled + BOOLOPT("GBA/SkipIntro", wxTRANSLATE("Skip intro"), gopts.skip_intro), +#endif + BOOLOPT("GBA/UseBiosFile", wxTRANSLATE("Use the specified BIOS file"), gopts.gba_use_bios), + BOOLOPT("General/ApplyPatches", wxTRANSLATE("Apply IPS/UPS/IPF patches if found"), gopts.apply_patches), + BOOLOPT("General/AutoLoadLastState", wxTRANSLATE("Automatically load last saved state"), gopts.autoload_state), + BOOLOPT("General/AutoSaveCheatList", wxTRANSLATE("Automatically save and load cheat list"), gopts.autoload_cheats), + STROPT ("General/BatteryDir", wxTRANSLATE("Directory to store game save files (relative paths are relative to ROM; blank is config dir)"), gopts.battery_dir), + ENUMOPT("General/CaptureFormat", wxTRANSLATE("Screen capture file format"), gopts.cap_format, wxTRANSLATE("png|bmp")), + BOOLOPT("General/EnableCheats", wxTRANSLATE("Enable cheats"), cheatsEnabled), + BOOLOPT("General/FreezeRecent", wxTRANSLATE("Freeze recent load list"), gopts.recent_freeze), + BOOLOPT("General/PauseWhenInactive", wxTRANSLATE("Pause game when main window loses focus"), gopts.defocus_pause), + STROPT ("General/RecordingDir", wxTRANSLATE("Directory to store A/V and game recordings (relative paths are relative to ROM)"), gopts.recording_dir), + INTOPT ("General/RewindInterval", wxTRANSLATE("Number of seconds between rewind snapshots (0 to disable)"), gopts.rewind_interval, 0, 600), + STROPT ("General/ScreenshotDir", wxTRANSLATE("Directory to store screenshots (relative paths are relative to ROM)"), gopts.scrshot_dir), + BOOLOPT("General/SkipBios", wxTRANSLATE("Skip BIOS initialization"), skipBios), + STROPT ("General/StateDir", wxTRANSLATE("Directory to store saved state files (relative paths are relative to BatteryDir)"), gopts.state_dir), + BOOLOPT("General/StateLoadNoBattery", wxTRANSLATE("Do not overwrite native (battery) save when loading state"), skipSaveGameBattery), + BOOLOPT("General/StateLoadNoCheat", wxTRANSLATE("Do not overwrite cheat list when loading state"), skipSaveGameCheats), + INTOPT ("General/Throttle", wxTRANSLATE("Throttle game speed, even when accelerated (0-1000%, 0 = disabled)"), gopts.throttle, 0, 1000), + { wxT("Joypad/*/*"), wxTRANSLATE("The parameter Joypad//