Add new hooks to provide some WUT features like malloc wrapping, sd card access or using of std::threads

Updated the example plugins
This commit is contained in:
Maschell 2019-11-18 11:50:03 +01:00
parent d2e557412f
commit 3a0b5122c7
9 changed files with 229 additions and 121 deletions

View File

@ -51,6 +51,8 @@ include $(WUPSDIR)/plugin_makefile.mk
# options for code generation # options for code generation
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
MACHDEP = -DESPRESSO -mcpu=750 -meabi -mhard-float
# -Os: optimise size # -Os: optimise size
# -Wall: generate lots of warnings # -Wall: generate lots of warnings
# -D__wiiu__: define the symbol __wiiu__ (used in some headers) # -D__wiiu__: define the symbol __wiiu__ (used in some headers)
@ -60,27 +62,24 @@ include $(WUPSDIR)/plugin_makefile.mk
# -nostartfiles: Do not use the standard system startup files when linking # -nostartfiles: Do not use the standard system startup files when linking
# -ffunction-sections: split up functions so linker can garbage collect # -ffunction-sections: split up functions so linker can garbage collect
# -fdata-sections: split up data so linker can garbage collect # -fdata-sections: split up data so linker can garbage collect
COMMON_CFLAGS := -Os -Wall -mcpu=750 -meabi -mhard-float -D__WIIU__ -nostartfiles -ffunction-sections -fdata-sections -Wl,-q $(COMMON_CFLAGS) COMMON_CFLAGS := -O0 -Wall $(MACHDEP) -meabi -ffunction-sections -fdata-sections -Wl,-q $(COMMON_CFLAGS)
CFLAGS += -D__WIIU__ -D__WUT__
# -x c: compile as c code # -x c: compile as c code
# -std=c11: use the c11 standard # -std=c11: use the c11 standard
CFLAGS := $(COMMON_CFLAGS) -x c -std=gnu11 $(CFLAGS) CFLAGS := $(COMMON_CFLAGS) -x c -std=gnu11 $(CFLAGS)
# -x c: compile as c++ code # -x c++: compile as c++ code
# -std=gnu++11: use the c++11 standard # -std=gnu++11: use the c++11 standard
CXXFLAGS := $(COMMON_CFLAGS) -x c++ -std=gnu++11 $(CXXFLAGS) CXXFLAGS := $(COMMON_CFLAGS) -x c++ -std=gnu++11 $(CXXFLAGS)
ifeq ($(DO_LOGGING), 1)
CFLAGS += -D__LOGGING__
CXXFLAGS += -D__LOGGING__
endif
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# any extra ld flags # any extra ld flags
#-------------------------------------------------------------------------------- #--------------------------------------------------------------------------------
# --gc-sections: remove unneeded symbols # --gc-sections: remove unneeded symbols
# -Map: generate a map file # -Map: generate a map file
LDFLAGS += -Wl,-Map,$(notdir $@).map,--gc-sections LDFLAGS += $(ARCH) -Wl,-Map,$(notdir $@).map,--gc-sections,-wrap,__gxx_personality_v0
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
@ -97,33 +96,6 @@ LIBS +=
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
LIBDIRS += LIBDIRS +=
NEEDS_WUT := 0
ifeq ($(WUT_ENABLE_CPP), 1)
WUT_ENABLE_NEWLIB := 1
LDFLAGS += -Wl,-whole-archive,-lwutstdc++,-no-whole-archive
NEEDS_WUT := 1
endif
ifeq ($(WUT_ENABLE_NEWLIB), 1)
LDFLAGS += -Wl,-whole-archive,-lwutnewlib,-no-whole-archive
NEEDS_WUT := 1
endif
ifeq ($(WUT_DEFAULT_MALLOC), 1)
LDFLAGS += -Wl,-whole-archive,-lwutmalloc,-no-whole-archive
NEEDS_WUT := 1
endif
ifeq ($(NEEDS_WUT), 1)
ifeq ($(strip $(WUT_ROOT)),)
$(error "Please set WUT_ROOT in your environment. export WUT_ROOT=<path to>wut)
endif
CFLAGS += -D__WUT__
CXXFLAGS += -D__WUT__
endif
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional # no real need to edit anything past this point unless you need to add additional
# rules for different file extensions # rules for different file extensions
@ -230,7 +202,7 @@ $(OUTPUT) : $(OFILES)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
%.o: %.S %.o: %.S
@echo $(notdir $<) @echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(INCLUDE_FULL) -c $< -o $@ $(ERROR_FILTER) @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(INCLUDE_FULL) -c $< -o $@ $(ERROR_FILTER)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
%.png.o : %.png %.png.o : %.png

View File

@ -1,20 +1,3 @@
# Compiling the projects with libutils logging code?
DO_LOGGING := 1
# Links against the wut implementation of newlib, this is useful for using any function
# from the C standard library
WUT_ENABLE_NEWLIB := 0
# Links against the wut implementation of stdcpp, this is useful for using any function
# from the C++ standard library. This will enable WUT_ENABLE_NEWLIB if you have not already done so.
WUT_ENABLE_CPP := 0
# By default newlib will allocate 90% of the default heap for use with sbrk & malloc,
# if this is unacceptable to you then you should use this as it replaces the newlib
# malloc functions which ones which redirect to the CafeOS default heap functions
# such as MEMAllocFromDefaultHeap.
WUT_DEFAULT_MALLOC := 0
# Target filename # Target filename
TARGET := $(notdir $(CURDIR)).mod TARGET := $(notdir $(CURDIR)).mod
@ -43,12 +26,12 @@ LDFLAGS :=
# list of directories containing libraries, this must be the top level containing # list of directories containing libraries, this must be the top level containing
# include and lib # include and lib
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
LIBDIRS := $(WUPSDIR) LIBDIRS := $(WUPSDIR) $(WUT_ROOT) $(PORTLIBS)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project # any extra libraries we wish to link with the project
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
LIBS := -lwups LIBS := -lwups -lwut
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# Will be added to the final lib paths # Will be added to the final lib paths

View File

@ -18,10 +18,18 @@ WUPS_PLUGIN_LICENSE("BSD");
https://github.com/Maschell/WiiUPluginSystem/wiki/Using-hooks https://github.com/Maschell/WiiUPluginSystem/wiki/Using-hooks
**/ **/
// FS Access (replaces open/close/write/read etc. functions) /**
WUPS_FS_ACCESS()
// Overlay access WUPS_USE_WUT_MALLOC() // Use the wut malloc wrapper
//WUPS_ALLOW_OVERLAY() WUPS_USE_WUT_NEWLIB() // Use serveral function implementations
WUPS_USE_WUT_DEVOPTAB() // Use wut devoptab for SD access
WUPS_USE_WUT_STDCPP() // Use wut cpp wrappers
WUPS_USE_WUT_CRT() // Use all of them
**/
WUPS_USE_WUT_MALLOC() // Use the wut malloc wrapper
// Gets called once when the loader exits. // Gets called once when the loader exits.
INITIALIZE_PLUGIN(){ INITIALIZE_PLUGIN(){
@ -38,13 +46,8 @@ ON_FUNCTIONS_PATCHED(){
// Get called when ever GX2_VSYNC() was called (on each frame) // Get called when ever GX2_VSYNC() was called (on each frame)
ON_VYSNC(){ ON_VYSNC(){
} }
// Gets called whenever the status of the running application changes.
ON_APP_STATUS_CHANGED(status){
}
// Called whenever an application is ending // Called whenever an application is ending
ON_APPLICATION_ENDING(){ ON_APPLICATION_END(){
} }
// Gets called once when the loader is loaded again at the plugins will be unloaded // Gets called once when the loader is loaded again at the plugins will be unloaded

View File

@ -4,6 +4,4 @@ ASFLAGS := -mregnames
# -wrap: wrap function # -wrap: wrap function
# -q: Leave relocation sections and contents in fully linked executables # -q: Leave relocation sections and contents in fully linked executables
LDFLAGS += -T $(WUPSDIR)/wups.ld \ LDFLAGS += -T $(WUPSDIR)/wups.ld -Wl,-q
-Wl,-wrap,open,-wrap,close,-wrap,write,-wrap,read,-wrap,lseek,-wrap,stat,-wrap,fstat,-wrap,opendir,-wrap,closedir,-wrap,readdir,-wrap,mkdir \
-Wl,-q

View File

@ -2,7 +2,7 @@
TARGET := $(notdir $(CURDIR)).mod TARGET := $(notdir $(CURDIR)).mod
# Source directories # Source directories
SOURCES := src SOURCES := src src/utils
# Data directories # Data directories
DATA := DATA :=
@ -31,7 +31,7 @@ LIBDIRS := $(WUPSDIR) $(WUT_ROOT) $(PORTLIBS)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project # any extra libraries we wish to link with the project
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
LIBS := -lwups -lwut LIBS := -lwut -lwups
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# Will be added to the final lib paths # Will be added to the final lib paths

View File

@ -1,8 +1,11 @@
#include <wups.h> #include <wups.h>
#include <whb/libmanager.h>
#include <malloc.h> #include <malloc.h>
#include <string.h> #include <string.h>
#include <nsysnet/socket.h> #include <nsysnet/socket.h>
#include <utils/logger.h> #include <utils/logger.h>
#include <coreinit/time.h>
#include <coreinit/thread.h>
#include <coreinit/filesystem.h> #include <coreinit/filesystem.h>
/** /**
@ -15,26 +18,31 @@ WUPS_PLUGIN_VERSION("v1.0");
WUPS_PLUGIN_AUTHOR("Maschell"); WUPS_PLUGIN_AUTHOR("Maschell");
WUPS_PLUGIN_LICENSE("BSD"); WUPS_PLUGIN_LICENSE("BSD");
/**
Add this to one of your projects file to have access to SD/USB.
**/
WUPS_FS_ACCESS()
/**
Add this to one of your projects file to be able to create overlays.
**/
WUPS_ALLOW_OVERLAY()
/** /**
All of this defines can be used in ANY file. All of this defines can be used in ANY file.
It's possible to split it up into multiple files. It's possible to split it up into multiple files.
**/ **/
/**
WUPS_USE_WUT_MALLOC() // Use the wut malloc wrapper
WUPS_USE_WUT_NEWLIB() // Use serveral function implementations
WUPS_USE_WUT_DEVOPTAB() // Use wut devoptab for SD access
WUPS_USE_WUT_STDCPP() // Use wut cpp wrappers
WUPS_USE_WUT_CRT() // Use all of them
**/
WUPS_USE_WUT_MALLOC() // Use the wut malloc wrapper
/** /**
Get's called ONCE when the loader exits, but BEFORE the ON_APPLICATION_START gets called or functions are overridden. Get's called ONCE when the loader exits, but BEFORE the ON_APPLICATION_START gets called or functions are overridden.
**/ **/
INITIALIZE_PLUGIN(){ INITIALIZE_PLUGIN(){
socket_lib_init(); WHBInitializeSocketLibrary();
log_init(); log_init();
DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!\n"); DEBUG_FUNCTION_LINE("INITIALIZE_PLUGIN of example_plugin!\n");
} }
@ -44,8 +52,6 @@ INITIALIZE_PLUGIN(){
The overridden functions are restored before this is getting called. The overridden functions are restored before this is getting called.
**/ **/
DEINITIALIZE_PLUGIN(){ DEINITIALIZE_PLUGIN(){
socket_lib_init();
log_init();
DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!\n"); DEBUG_FUNCTION_LINE("DEINITIALIZE_PLUGIN of example_plugin!\n");
} }
@ -55,7 +61,7 @@ DEINITIALIZE_PLUGIN(){
Make sure to initialize all functions you're using in the overridden functions! Make sure to initialize all functions you're using in the overridden functions!
**/ **/
ON_APPLICATION_START(){ ON_APPLICATION_START(){
socket_lib_init(); WHBInitializeSocketLibrary();
log_init(); log_init();
DEBUG_FUNCTION_LINE("ON_APPLICATION_START of example_plugin!\n"); DEBUG_FUNCTION_LINE("ON_APPLICATION_START of example_plugin!\n");
@ -64,35 +70,10 @@ ON_APPLICATION_START(){
/** /**
Gets called when an application ends. A good place for freeing memory. Gets called when an application ends. A good place for freeing memory.
**/ **/
ON_APPLICATION_ENDING(){ ON_APPLICATION_END(){
DEBUG_FUNCTION_LINE("ON_APPLICATION_ENDING of example_plugin!\n"); DEBUG_FUNCTION_LINE("ON_APPLICATION_ENDING of example_plugin!\n");
} }
/**
Gets called on each frame.
**/
ON_VYSNC(){
DEBUG_FUNCTION_LINE("ON_VYSNC of example_plugin!\n");
}
/**
Gets called whenever the application status changes.
Possible values of "status":
WUPS_APP_STATUS_FOREGROUND, App is now running in foreground
WUPS_APP_STATUS_BACKGROUND App is now running in background
WUPS_APP_STATUS_CLOSED App is going to be closed
**/
ON_APP_STATUS_CHANGED(status){
if(status == WUPS_APP_STATUS_FOREGROUND){
DEBUG_FUNCTION_LINE("ON_APP_STATUS_CHANGED of example_plugin! App is now in foreground\n");
} else if(status == WUPS_APP_STATUS_BACKGROUND){
DEBUG_FUNCTION_LINE("ON_APP_STATUS_CHANGED of example_plugin! App is now in background\n");
} else if(status == WUPS_APP_STATUS_CLOSED){
DEBUG_FUNCTION_LINE("ON_APP_STATUS_CHANGED of example_plugin! App is now going to be closed\n");
}
}
/** /**
This defines a function replacement. This defines a function replacement.
It allows to replace the system function with an own function. It allows to replace the system function with an own function.

View File

@ -0,0 +1,82 @@
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <utils/logger.h>
#include <nsysnet/socket.h>
#include <coreinit/debug.h>
#include <coreinit/systeminfo.h>
#include <coreinit/thread.h>
static int log_socket __attribute__((section(".data")))= -1;
static struct sockaddr_in connect_addr __attribute__((section(".data")));
static volatile int log_lock __attribute__((section(".data"))) = 0;
void log_init_() {
int broadcastEnable = 1;
log_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (log_socket < 0)
return;
setsockopt(log_socket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));
memset(&connect_addr, 0, sizeof(struct sockaddr_in));
connect_addr.sin_family = AF_INET;
connect_addr.sin_port = 4405;
connect_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
}
void log_print_(const char *str) {
// socket is always 0 initially as it is in the BSS
if(log_socket < 0) {
return;
}
while(log_lock)
OSSleepTicks(OSMicrosecondsToTicks(1000));
log_lock = 1;
int len = strlen(str);
int ret;
while (len > 0) {
int block = len < 1400 ? len : 1400; // take max 1400 bytes per UDP packet
ret = sendto(log_socket, str, block, 0, (struct sockaddr *)&connect_addr, sizeof(struct sockaddr_in));
if(ret < 0)
break;
len -= ret;
str += ret;
}
log_lock = 0;
}
void OSFatal_printf(const char *format, ...) {
char tmp[512];
tmp[0] = 0;
va_list va;
va_start(va, format);
if((vsprintf(tmp, format, va) >= 0)) {
OSFatal(tmp);
}
va_end(va);
}
void log_printf_(const char *format, ...) {
if(log_socket < 0) {
return;
}
char tmp[512];
tmp[0] = 0;
va_list va;
va_start(va, format);
if((vsprintf(tmp, format, va) >= 0)) {
log_print_(tmp);
}
va_end(va);
}

View File

@ -0,0 +1,36 @@
#ifndef __LOGGER_H_
#define __LOGGER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
void log_init_();
//void log_deinit_(void);
void log_print_(const char *str);
void log_printf_(const char *format, ...);
void OSFatal_printf(const char *format, ...);
#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__)
#define OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \
OSFatal_printf("[%s]%s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0)
#define log_init() log_init_()
//#define log_deinit() log_deinit_()
#define log_print(str) log_print_(str)
#define log_printf(FMT, ARGS...) log_printf_(FMT, ## ARGS);
#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \
log_printf("[%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0)
#ifdef __cplusplus
}
#endif
#endif

View File

@ -33,10 +33,17 @@ extern "C" {
} }
typedef enum wups_loader_hook_type_t { typedef enum wups_loader_hook_type_t {
WUPS_LOADER_HOOK_INIT_FS, /* Only for internal usage */
WUPS_LOADER_HOOK_INIT_OVERLAY, /* Only for internal usage */ WUPS_LOADER_HOOK_INIT_OVERLAY, /* Only for internal usage */
WUPS_LOADER_HOOK_INIT_KERNEL, /* Only for internal usage */ WUPS_LOADER_HOOK_INIT_KERNEL, /* Only for internal usage */
WUPS_LOADER_HOOK_INIT_VID_MEM, /* Only for internal usage */ WUPS_LOADER_HOOK_INIT_VID_MEM, /* Only for internal usage */
WUPS_LOADER_HOOK_INIT_WUT_MALLOC,
WUPS_LOADER_HOOK_FINI_WUT_MALLOC,
WUPS_LOADER_HOOK_INIT_WUT_DEVOPTAB,
WUPS_LOADER_HOOK_FINI_WUT_DEVOPTAB,
WUPS_LOADER_HOOK_INIT_WUT_NEWLIB,
WUPS_LOADER_HOOK_FINI_WUT_NEWLIB,
WUPS_LOADER_HOOK_INIT_WUT_STDCPP,
WUPS_LOADER_HOOK_FINI_WUT_STDCPP,
WUPS_LOADER_HOOK_INIT_PLUGIN, /* Called when exiting the plugin loader */ WUPS_LOADER_HOOK_INIT_PLUGIN, /* Called when exiting the plugin loader */
WUPS_LOADER_HOOK_DEINIT_PLUGIN, /* Called when re-entering the plugin loader */ WUPS_LOADER_HOOK_DEINIT_PLUGIN, /* Called when re-entering the plugin loader */
@ -67,18 +74,9 @@ typedef struct wups_loader_vid_buffer_t {
} wups_loader_vid_buffer_t; } wups_loader_vid_buffer_t;
typedef struct wups_loader_app_started_args_t { typedef struct wups_loader_app_started_args_t {
bool sd_mounted;
bool usb_mounted;
bool kernel_access; bool kernel_access;
} wups_loader_app_started_args_t; } wups_loader_app_started_args_t;
#define WUPS_FS_ACCESS() \
void init_fs(wups_loader_init_fs_args_t);\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_FS,init_fs); \
void init_fs(wups_loader_init_fs_args_t args){ \
WUPS_InitFS(args);\
}
#define WUPS_ALLOW_OVERLAY() \ #define WUPS_ALLOW_OVERLAY() \
void init_overlay(wups_loader_init_overlay_args_t);\ void init_overlay(wups_loader_init_overlay_args_t);\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_OVERLAY,init_overlay); \ WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_OVERLAY,init_overlay); \
@ -165,6 +163,61 @@ typedef struct wups_loader_app_started_args_t {
WUPS_HOOK_EX(WUPS_LOADER_HOOK_VID_TV_DRAW,on_tv_to_scan_buf); \ WUPS_HOOK_EX(WUPS_LOADER_HOOK_VID_TV_DRAW,on_tv_to_scan_buf); \
void on_tv_to_scan_buf(wups_loader_vid_buffer_t myargs) void on_tv_to_scan_buf(wups_loader_vid_buffer_t myargs)
#define WUPS_USE_WUT_MALLOC() \
extern "C" void __init_wut_malloc(); \
void on_init_wut_malloc(){ \
__init_wut_malloc(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_WUT_MALLOC,on_init_wut_malloc); \
extern "C" void __fini_wut_malloc(); \
void on_fini_wut_malloc(){ \
__fini_wut_malloc(); \
} \
WUPS_HOOK_EX(WUPS_LOADER_HOOK_FINI_WUT_MALLOC,on_fini_wut_malloc); \
#define WUPS_USE_WUT_DEVOPTAB() \
extern "C" void __init_wut_devoptab(); \
void on_init_wut_devoptab(){ \
__init_wut_devoptab(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_WUT_DEVOPTAB,on_init_wut_devoptab); \
extern "C" void __fini_wut_devoptab(); \
void on_fini_wut_devoptab(){ \
__fini_wut_devoptab(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_FINI_WUT_MALLOC,on_fini_wut_devoptab);
#define WUPS_USE_WUT_NEWLIB() \
extern "C" void __init_wut_newlib(); \
void on_init_wut_newlib(){ \
__init_wut_newlib(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_WUT_NEWLIB,on_init_wut_newlib); \
extern "C" void __fini_wut_newlib(); \
void on_fini_wut_newlib(){ \
__fini_wut_newlib(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_FINI_WUT_NEWLIB,on_fini_wut_newlib);
#define WUPS_USE_WUT_STDCPP() \
extern "C" void __init_wut_stdcpp() __attribute__((weak)); \
void on_init_wut_stdcpp(){ \
__init_wut_stdcpp(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_INIT_WUT_STDCPP,on_init_wut_stdcpp); \
extern "C" void __fini_wut_stdcpp() __attribute__((weak)); \
void on_fini_wut_stdcpp(){ \
__fini_wut_stdcpp(); \
}\
WUPS_HOOK_EX(WUPS_LOADER_HOOK_FINI_WUT_STDCPP,on_fini_wut_stdcpp);
#define WUPS_USE_WUT_CRT() \
WUPS_USE_WUT_MALLOC() \
WUPS_USE_WUT_DEVOPTAB() \
WUPS_USE_WUT_NEWLIB() \
WUPS_USE_WUT_STDCPP()
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif