2012-04-10 10:03:30 +00:00
|
|
|
// PC implementation of the framework.
|
2012-04-10 09:59:57 +00:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#include <Windows.h>
|
|
|
|
#include <shlobj.h>
|
|
|
|
#include <shlwapi.h>
|
|
|
|
#include <ShellAPI.h>
|
2012-04-15 22:35:11 +00:00
|
|
|
#else
|
|
|
|
#include <pwd.h>
|
2012-11-03 02:34:06 +00:00
|
|
|
#include <unistd.h>
|
2012-04-10 09:59:57 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#include "SDL/SDL.h"
|
|
|
|
#include "SDL/SDL_timer.h"
|
|
|
|
#include "SDL/SDL_audio.h"
|
2012-09-01 13:16:23 +00:00
|
|
|
#include "SDL/SDL_video.h"
|
2012-04-10 09:59:57 +00:00
|
|
|
|
|
|
|
#include "base/display.h"
|
|
|
|
#include "base/logging.h"
|
|
|
|
#include "base/timeutil.h"
|
|
|
|
#include "gfx_es2/glsl_program.h"
|
|
|
|
#include "file/zip_read.h"
|
|
|
|
#include "input/input_state.h"
|
|
|
|
#include "base/NativeApp.h"
|
2012-06-03 17:01:08 +00:00
|
|
|
#include "net/resolve.h"
|
2012-04-10 09:59:57 +00:00
|
|
|
|
|
|
|
// Simple implementations of System functions
|
|
|
|
|
2012-07-16 13:00:52 +00:00
|
|
|
|
|
|
|
|
2012-04-10 09:59:57 +00:00
|
|
|
void SystemToast(const char *text) {
|
2012-07-05 21:30:35 +00:00
|
|
|
#ifdef _WIN32
|
2012-07-06 20:32:32 +00:00
|
|
|
MessageBox(0, text, "Toast!", MB_ICONINFORMATION);
|
2012-07-05 21:30:35 +00:00
|
|
|
#else
|
2012-07-06 20:32:32 +00:00
|
|
|
puts(text);
|
2012-07-05 21:30:35 +00:00
|
|
|
#endif
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ShowAd(int x, int y, bool center_x) {
|
|
|
|
// Ignore ads on PC
|
|
|
|
}
|
|
|
|
|
2012-04-16 21:30:13 +00:00
|
|
|
void ShowKeyboard() {
|
|
|
|
// Irrelevant on PC
|
|
|
|
}
|
|
|
|
|
2012-04-10 09:59:57 +00:00
|
|
|
void Vibrate(int length_ms) {
|
|
|
|
// Ignore on PC
|
|
|
|
}
|
|
|
|
|
|
|
|
void LaunchBrowser(const char *url)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
#else
|
2012-10-31 13:01:32 +00:00
|
|
|
ILOG("Would have gone to %s but LaunchBrowser is not implemented on this platform", url);
|
2012-04-10 09:59:57 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void LaunchMarket(const char *url)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
#else
|
2012-10-31 13:01:32 +00:00
|
|
|
ILOG("Would have gone to %s but LaunchMarket is not implemented on this platform", url);
|
2012-04-10 09:59:57 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void LaunchEmail(const char *email_address)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
ShellExecute(NULL, "open", (std::string("mailto:") + email_address).c_str(), NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
#else
|
2012-10-31 13:01:32 +00:00
|
|
|
ILOG("Would have opened your email client for %s but LaunchEmail is not implemented on this platform", email_address);
|
2012-04-10 09:59:57 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-10-28 10:37:10 +00:00
|
|
|
const int buttonMappings[14] = {
|
2012-10-31 13:01:32 +00:00
|
|
|
SDLK_x, //A
|
|
|
|
SDLK_s, //B
|
|
|
|
SDLK_z, //X
|
|
|
|
SDLK_a, //Y
|
2012-04-10 09:59:57 +00:00
|
|
|
SDLK_w, //LBUMPER
|
|
|
|
SDLK_q, //RBUMPER
|
|
|
|
SDLK_1, //START
|
2012-10-28 10:37:10 +00:00
|
|
|
SDLK_2, //SELECT
|
2012-10-31 13:01:32 +00:00
|
|
|
SDLK_UP, //UP
|
|
|
|
SDLK_DOWN, //DOWN
|
|
|
|
SDLK_LEFT, //LEFT
|
|
|
|
SDLK_RIGHT, //RIGHT
|
2012-10-31 11:12:24 +00:00
|
|
|
SDLK_m, //MENU
|
|
|
|
SDLK_BACKSPACE, //BACK
|
2012-04-10 09:59:57 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
void SimulateGamepad(const uint8 *keys, InputState *input) {
|
2012-07-06 20:32:32 +00:00
|
|
|
input->pad_buttons = 0;
|
|
|
|
input->pad_lstick_x = 0;
|
|
|
|
input->pad_lstick_y = 0;
|
|
|
|
input->pad_rstick_x = 0;
|
|
|
|
input->pad_rstick_y = 0;
|
2012-10-28 10:37:10 +00:00
|
|
|
for (int b = 0; b < 14; b++) {
|
2012-04-10 09:59:57 +00:00
|
|
|
if (keys[buttonMappings[b]])
|
|
|
|
input->pad_buttons |= (1<<b);
|
|
|
|
}
|
|
|
|
|
2012-10-31 13:01:32 +00:00
|
|
|
if (keys[SDLK_i])
|
2012-10-31 11:12:24 +00:00
|
|
|
input->pad_lstick_y=1;
|
2012-07-26 15:24:47 +00:00
|
|
|
else if (keys[SDLK_k])
|
2012-10-31 13:01:32 +00:00
|
|
|
input->pad_lstick_y=-1;
|
|
|
|
if (keys[SDLK_j])
|
2012-10-31 11:12:24 +00:00
|
|
|
input->pad_lstick_x=-1;
|
2012-07-26 15:24:47 +00:00
|
|
|
else if (keys[SDLK_l])
|
2012-10-31 13:01:32 +00:00
|
|
|
input->pad_lstick_x=1;
|
|
|
|
if (keys[SDLK_KP8])
|
2012-10-31 11:12:24 +00:00
|
|
|
input->pad_rstick_y=1;
|
2012-07-26 15:24:47 +00:00
|
|
|
else if (keys[SDLK_KP2])
|
2012-10-31 13:01:32 +00:00
|
|
|
input->pad_rstick_y=-1;
|
|
|
|
if (keys[SDLK_KP4])
|
2012-10-31 11:12:24 +00:00
|
|
|
input->pad_rstick_x=-1;
|
2012-07-26 15:24:47 +00:00
|
|
|
else if (keys[SDLK_KP6])
|
2012-10-31 13:01:32 +00:00
|
|
|
input->pad_rstick_x=1;
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
extern void mixaudio(void *userdata, Uint8 *stream, int len) {
|
2012-10-31 13:01:32 +00:00
|
|
|
NativeMix((short *)stream, len / 4);
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#undef main
|
|
|
|
#endif
|
|
|
|
int main(int argc, char *argv[]) {
|
2012-07-15 15:04:27 +00:00
|
|
|
/* // Xoom/Nexus 7 resolution. Other common tablet resolutions: 1024x600 , 1366x768
|
|
|
|
dp_xres = 1280;
|
|
|
|
dp_yres = 800;
|
|
|
|
|
2012-10-31 11:12:24 +00:00
|
|
|
*/
|
2012-04-10 09:59:57 +00:00
|
|
|
std::string app_name;
|
|
|
|
std::string app_name_nice;
|
2012-04-10 13:44:58 +00:00
|
|
|
|
2012-07-08 22:43:04 +00:00
|
|
|
float zoom = 1.0f;
|
2012-10-31 11:12:24 +00:00
|
|
|
bool tablet = false;
|
2012-07-06 20:32:32 +00:00
|
|
|
const char *zoomenv = getenv("ZOOM");
|
2012-10-31 11:12:24 +00:00
|
|
|
const char *tabletenv = getenv("TABLET");
|
2012-07-06 20:32:32 +00:00
|
|
|
if (zoomenv) {
|
|
|
|
zoom = atof(zoomenv);
|
|
|
|
}
|
2012-10-31 13:01:32 +00:00
|
|
|
if (tabletenv) {
|
|
|
|
tablet = (bool)atoi(tabletenv);
|
|
|
|
}
|
2012-10-31 11:12:24 +00:00
|
|
|
|
|
|
|
bool landscape;
|
|
|
|
NativeGetAppInfo(&app_name, &app_name_nice, &landscape);
|
|
|
|
|
|
|
|
if (landscape) {
|
|
|
|
if (tablet) {
|
|
|
|
pixel_xres = 1280 * zoom;
|
|
|
|
pixel_yres = 800 * zoom;
|
|
|
|
} else {
|
|
|
|
pixel_xres = 800 * zoom;
|
|
|
|
pixel_yres = 480 * zoom;
|
|
|
|
}
|
2012-04-10 13:44:58 +00:00
|
|
|
} else {
|
2012-10-31 11:12:24 +00:00
|
|
|
// PC development hack for more space
|
2012-07-15 15:04:27 +00:00
|
|
|
//pixel_xres = 1580 * zoom;
|
|
|
|
//pixel_yres = 1000 * zoom;
|
2012-10-31 11:12:24 +00:00
|
|
|
if (tablet) {
|
|
|
|
pixel_xres = 800 * zoom;
|
|
|
|
pixel_yres = 1280 * zoom;
|
2012-11-20 22:35:40 +00:00
|
|
|
} else {
|
|
|
|
pixel_xres = 480 * zoom;
|
|
|
|
pixel_yres = 800 * zoom;
|
2012-10-31 11:12:24 +00:00
|
|
|
}
|
2012-04-10 13:44:58 +00:00
|
|
|
}
|
2012-04-10 09:59:57 +00:00
|
|
|
|
2012-07-06 20:32:32 +00:00
|
|
|
net::Init();
|
2012-09-01 13:16:23 +00:00
|
|
|
#ifdef __APPLE__
|
2012-10-31 11:12:24 +00:00
|
|
|
// Make sure to request a somewhat modern GL context at least - the
|
2012-09-01 13:16:23 +00:00
|
|
|
// latest supported by MacOSX (really, really sad...)
|
|
|
|
// Requires SDL 2.0 (which is even more sad, as that hasn't been released yet)
|
|
|
|
//SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
|
|
//SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
|
|
|
|
#endif
|
2012-06-03 17:01:08 +00:00
|
|
|
|
2012-04-10 09:59:57 +00:00
|
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
|
|
|
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
|
|
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
|
|
|
|
|
2012-07-06 20:32:32 +00:00
|
|
|
if (SDL_SetVideoMode(pixel_xres, pixel_yres, 0, SDL_OPENGL) == NULL) {
|
2012-04-10 09:59:57 +00:00
|
|
|
fprintf(stderr, "SDL SetVideoMode failed: Unable to create OpenGL screen: %s\n", SDL_GetError());
|
|
|
|
SDL_Quit();
|
|
|
|
return(2);
|
|
|
|
}
|
2012-11-07 17:29:35 +00:00
|
|
|
|
2012-04-10 09:59:57 +00:00
|
|
|
SDL_WM_SetCaption(app_name_nice.c_str(), NULL);
|
|
|
|
|
|
|
|
if (GLEW_OK != glewInit()) {
|
|
|
|
printf("Failed to initialize glew!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (GLEW_VERSION_2_0) {
|
|
|
|
printf("OpenGL 2.0 or higher.\n");
|
|
|
|
} else {
|
|
|
|
printf("Sorry, this program requires OpenGL 2.0.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
// VFSRegister("temp/", new DirectoryAssetReader("E:\\Temp\\"));
|
|
|
|
TCHAR path[MAX_PATH];
|
|
|
|
SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path);
|
|
|
|
PathAppend(path, (app_name + "\\").c_str());
|
|
|
|
#else
|
2012-07-15 15:04:27 +00:00
|
|
|
// Mac / Linux
|
2012-10-31 11:12:24 +00:00
|
|
|
char path[512];
|
2012-09-17 17:26:28 +00:00
|
|
|
const char *the_path = getenv("HOME");
|
|
|
|
if (!the_path) {
|
2012-04-10 09:59:57 +00:00
|
|
|
struct passwd* pwd = getpwuid(getuid());
|
|
|
|
if (pwd)
|
2012-09-17 17:26:28 +00:00
|
|
|
the_path = pwd->pw_dir;
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
2012-10-31 11:12:24 +00:00
|
|
|
strcpy(path, the_path);
|
|
|
|
if (path[strlen(path)-1] != '/')
|
|
|
|
strcat(path, "/");
|
2012-04-10 09:59:57 +00:00
|
|
|
#endif
|
|
|
|
|
2012-09-28 08:01:01 +00:00
|
|
|
#ifdef _WIN32
|
2012-10-31 11:12:24 +00:00
|
|
|
NativeInit(argc, (const char **)argv, path, "D:\\", "BADCOFFEE");
|
2012-09-28 08:01:01 +00:00
|
|
|
#else
|
2012-08-31 11:11:40 +00:00
|
|
|
NativeInit(argc, (const char **)argv, path, "/tmp", "BADCOFFEE");
|
2012-09-28 08:01:01 +00:00
|
|
|
#endif
|
2012-07-15 15:04:27 +00:00
|
|
|
|
2012-10-31 11:12:24 +00:00
|
|
|
float density = 1.0f;
|
|
|
|
dp_xres = (float)pixel_xres * density / zoom;
|
|
|
|
dp_yres = (float)pixel_yres * density / zoom;
|
2012-07-15 15:04:27 +00:00
|
|
|
|
2012-07-06 20:32:32 +00:00
|
|
|
NativeInitGraphics();
|
|
|
|
|
2012-10-31 11:12:24 +00:00
|
|
|
float dp_xscale = (float)dp_xres / pixel_xres;
|
|
|
|
float dp_yscale = (float)dp_yres / pixel_yres;
|
2012-07-15 15:04:27 +00:00
|
|
|
|
2012-10-31 11:12:24 +00:00
|
|
|
printf("Pixels: %i x %i\n", pixel_xres, pixel_yres);
|
|
|
|
printf("Virtual pixels: %i x %i\n", dp_xres, dp_yres);
|
2012-07-15 15:04:27 +00:00
|
|
|
|
2012-07-06 20:32:32 +00:00
|
|
|
SDL_AudioSpec fmt;
|
|
|
|
fmt.freq = 44100;
|
|
|
|
fmt.format = AUDIO_S16;
|
|
|
|
fmt.channels = 2;
|
|
|
|
fmt.samples = 1024;
|
|
|
|
fmt.callback = &mixaudio;
|
|
|
|
fmt.userdata = (void *)0;
|
|
|
|
|
|
|
|
if (SDL_OpenAudio(&fmt, NULL) < 0) {
|
|
|
|
ELOG("Failed to open audio: %s", SDL_GetError());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Audio must be unpaused _after_ NativeInit()
|
|
|
|
SDL_PauseAudio(0);
|
|
|
|
|
|
|
|
InputState input_state;
|
|
|
|
int framecount = 0;
|
2012-05-06 21:21:26 +00:00
|
|
|
bool nextFrameMD = 0;
|
2012-10-31 11:12:24 +00:00
|
|
|
float t = 0;
|
2012-04-10 09:59:57 +00:00
|
|
|
while (true) {
|
2012-07-06 20:32:32 +00:00
|
|
|
input_state.accelerometer_valid = false;
|
|
|
|
input_state.mouse_valid = true;
|
2012-07-15 15:04:27 +00:00
|
|
|
int quitRequested = 0;
|
2012-10-31 11:12:24 +00:00
|
|
|
|
|
|
|
SDL_Event event;
|
2012-04-10 09:59:57 +00:00
|
|
|
while (SDL_PollEvent(&event)) {
|
2012-10-31 11:12:24 +00:00
|
|
|
float mx = event.motion.x * dp_xscale;
|
|
|
|
float my = event.motion.y * dp_yscale;
|
2012-07-15 15:04:27 +00:00
|
|
|
|
2012-04-10 09:59:57 +00:00
|
|
|
if (event.type == SDL_QUIT) {
|
2012-07-15 15:04:27 +00:00
|
|
|
quitRequested = 1;
|
2012-04-10 09:59:57 +00:00
|
|
|
} else if (event.type == SDL_KEYDOWN) {
|
|
|
|
if (event.key.keysym.sym == SDLK_ESCAPE) {
|
2012-07-15 15:04:27 +00:00
|
|
|
quitRequested = 1;
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
|
|
|
} else if (event.type == SDL_MOUSEMOTION) {
|
2012-07-26 15:24:47 +00:00
|
|
|
input_state.pointer_x[0] = mx;
|
|
|
|
input_state.pointer_y[0] = my;
|
2012-10-31 11:12:24 +00:00
|
|
|
NativeTouch(0, mx, my, 0, TOUCH_MOVE);
|
2012-04-10 09:59:57 +00:00
|
|
|
} else if (event.type == SDL_MOUSEBUTTONDOWN) {
|
|
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
2012-07-06 20:32:32 +00:00
|
|
|
//input_state.mouse_buttons_down = 1;
|
2012-07-26 15:24:47 +00:00
|
|
|
input_state.pointer_down[0] = true;
|
2012-05-06 21:21:26 +00:00
|
|
|
nextFrameMD = true;
|
2012-10-31 11:12:24 +00:00
|
|
|
NativeTouch(0, mx, my, 0, TOUCH_DOWN);
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
|
|
|
} else if (event.type == SDL_MOUSEBUTTONUP) {
|
|
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
2012-07-26 15:24:47 +00:00
|
|
|
input_state.pointer_down[0] = false;
|
2012-07-06 20:32:32 +00:00
|
|
|
nextFrameMD = false;
|
|
|
|
//input_state.mouse_buttons_up = 1;
|
2012-10-31 11:12:24 +00:00
|
|
|
NativeTouch(0, mx, my, 0, TOUCH_UP);
|
2012-07-06 20:32:32 +00:00
|
|
|
}
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-15 15:04:27 +00:00
|
|
|
if (quitRequested)
|
2012-07-06 20:32:32 +00:00
|
|
|
break;
|
2012-05-06 21:21:26 +00:00
|
|
|
|
2012-07-15 15:04:27 +00:00
|
|
|
const uint8 *keys = (const uint8 *)SDL_GetKeyState(NULL);
|
2012-07-06 20:32:32 +00:00
|
|
|
if (keys[SDLK_ESCAPE])
|
|
|
|
break;
|
2012-04-10 09:59:57 +00:00
|
|
|
SimulateGamepad(keys, &input_state);
|
2012-07-06 20:32:32 +00:00
|
|
|
UpdateInputState(&input_state);
|
|
|
|
NativeUpdate(input_state);
|
|
|
|
NativeRender();
|
2012-10-26 16:42:17 +00:00
|
|
|
|
2012-10-31 11:12:24 +00:00
|
|
|
EndInputState(&input_state);
|
2012-10-26 16:42:17 +00:00
|
|
|
|
2012-07-06 20:32:32 +00:00
|
|
|
if (framecount % 60 == 0) {
|
2012-10-31 11:12:24 +00:00
|
|
|
// glsl_refresh(); // auto-reloads modified GLSL shaders once per second.
|
2012-07-06 20:32:32 +00:00
|
|
|
}
|
2012-10-31 11:12:24 +00:00
|
|
|
|
2012-04-10 09:59:57 +00:00
|
|
|
SDL_GL_SwapBuffers();
|
|
|
|
|
2012-07-15 23:10:03 +00:00
|
|
|
// Simple frame rate limiting
|
2012-07-15 15:04:27 +00:00
|
|
|
while (time_now() < t + 1.0f/60.0f) {
|
2012-04-10 09:59:57 +00:00
|
|
|
sleep_ms(0);
|
2012-07-06 20:32:32 +00:00
|
|
|
time_update();
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
2012-07-06 20:32:32 +00:00
|
|
|
time_update();
|
2012-04-10 09:59:57 +00:00
|
|
|
t = time_now();
|
2012-07-06 20:32:32 +00:00
|
|
|
framecount++;
|
2012-04-10 09:59:57 +00:00
|
|
|
}
|
2012-07-06 20:32:32 +00:00
|
|
|
// Faster exit, thanks to the OS. Remove this if you want to debug shutdown
|
2012-10-31 11:12:24 +00:00
|
|
|
// The speed difference is only really noticable on Linux. On Windows you do notice it though
|
2012-07-15 23:10:03 +00:00
|
|
|
// exit(0);
|
2012-04-10 09:59:57 +00:00
|
|
|
|
2012-07-06 20:32:32 +00:00
|
|
|
NativeShutdownGraphics();
|
|
|
|
SDL_PauseAudio(1);
|
|
|
|
NativeShutdown();
|
|
|
|
SDL_CloseAudio();
|
2012-04-10 09:59:57 +00:00
|
|
|
SDL_Quit();
|
2012-10-31 11:12:24 +00:00
|
|
|
net::Shutdown();
|
|
|
|
exit(0);
|
2012-04-10 09:59:57 +00:00
|
|
|
return 0;
|
|
|
|
}
|