diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3d5220f60..7939562ee 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,16 +8,12 @@ + + + - - - - - diff --git a/android/app/src/main/java/com/classicube/MainActivity.java b/android/app/src/main/java/com/classicube/MainActivity.java index 6726485ff..8972dc577 100644 --- a/android/app/src/main/java/com/classicube/MainActivity.java +++ b/android/app/src/main/java/com/classicube/MainActivity.java @@ -130,7 +130,15 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { void startGameAsync() { Log.i("CC_WIN", "handing off to native.."); - System.loadLibrary("classicube"); + try { + System.loadLibrary("classicube"); + } catch (UnsatisfiedLinkError ex) { + ex.printStackTrace(); + showAlert("Failed to start", ex.getMessage()); + return; + } + + gameRunning = true; runGameAsync(); } @@ -157,7 +165,6 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { } if (!gameRunning) startGameAsync(); - gameRunning = true; super.onCreate(savedInstanceState); } @@ -392,7 +399,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { setContentView(curView); curView.requestFocus(); - if (fullscreen) goFullscreen(); + if (fullscreen) setUIVisibility(FULLSCREEN_FLAGS); } class LauncherView extends SurfaceView { @@ -516,7 +523,10 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { //ApplicationInfo info = getPackageManager().getApplicationInfo(name, 0); ApplicationInfo info = getApplicationInfo(); File apkFile = new File(info.sourceDir); - return apkFile.lastModified(); + + // https://developer.android.com/reference/java/io/File#lastModified() + // lastModified is returned in milliseconds + return apkFile.lastModified() / 1000; } catch (Exception ex) { return 0; } @@ -606,7 +616,7 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { //final Activity activity = this; runOnUiThread(new Runnable() { public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this, AlertDialog.THEME_HOLO_DARK); + AlertDialog.Builder dlg = new AlertDialog.Builder(MainActivity.this); dlg.setTitle(title); dlg.setMessage(message); dlg.setPositiveButton("Close", new DialogInterface.OnClickListener() { @@ -623,29 +633,30 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { // TODO: this fails because multiple dialog boxes show } - public int getWindowState() { - return fullscreen ? 1 : 0; - } - - void goFullscreen() { - curView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + public int getWindowState() { return fullscreen ? 1 : 0; } + final static int FULLSCREEN_FLAGS = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + void setUIVisibility(int flags) { + if (curView == null) return; + try { + curView.setSystemUiVisibility(flags); + } catch (NoSuchMethodError ex) { + // Not available on API < 11 (Android 3.0) + ex.printStackTrace(); + } } public void enterFullscreen() { fullscreen = true; runOnUiThread(new Runnable() { - public void run() { - if (curView != null) goFullscreen(); - } + public void run() { setUIVisibility(FULLSCREEN_FLAGS); } }); } public void exitFullscreen() { fullscreen = false; runOnUiThread(new Runnable() { - public void run() { - if (curView != null) curView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); - } + public void run() { setUIVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } }); } @@ -703,16 +714,19 @@ public class MainActivity extends Activity implements SurfaceHolder.Callback2 { int len; try { conn.connect(); - Map> all = conn.getHeaderFields(); - - for (Map.Entry> h : all.entrySet()) { - String key = h.getKey(); - for (String value : h.getValue()) { - if (key == null) { - httpParseHeader(value); - } else { - httpParseHeader(key + ":" + value); - } + // Some implementations also provide this as getHeaderField(0), but some don't + httpParseHeader("HTTP/1.1 " + conn.getResponseCode() + " MSG"); + + // Legitimate webservers aren't going to reply with over 200 headers + for (int i = 0; i < 200; i++) { + String key = conn.getHeaderFieldKey(i); + String val = conn.getHeaderField(i); + if (key == null && val == null) break; + + if (key == null) { + httpParseHeader(val); + } else { + httpParseHeader(key + ":" + val); } } diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ccicon.png similarity index 100% rename from android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to android/app/src/main/res/mipmap-hdpi/ccicon.png diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ccicon.png similarity index 100% rename from android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to android/app/src/main/res/mipmap-mdpi/ccicon.png diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ccicon.png similarity index 100% rename from android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to android/app/src/main/res/mipmap-xhdpi/ccicon.png diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ccicon.png similarity index 100% rename from android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to android/app/src/main/res/mipmap-xxhdpi/ccicon.png diff --git a/misc/build_server.py b/misc/build_server.py new file mode 100644 index 000000000..469d2c101 --- /dev/null +++ b/misc/build_server.py @@ -0,0 +1,64 @@ +from http.server import HTTPServer, SimpleHTTPRequestHandler +import os +import subprocess + +build_files = { + '/ClassiCube.exe' : 'cc-w32-d3d.exe', '/ClassiCube.opengl.exe' : 'cc-w32-ogl.exe', + '/ClassiCube.64.exe' : 'cc-w64-d3d.exe', '/ClassiCube.64-opengl.exe' : 'cc-w64-ogl.exe', + '/ClassiCube.32' : 'cc-nix32', '/ClassiCube' : 'cc-nix64', + '/ClassiCube.osx' : 'cc-osx32', '/ClassiCube.64.osx' : 'cc-osx64', + '/ClassiCube.js' : 'cc.js', '/ClassiCube.apk' : 'cc.apk', + '/ClassiCube.rpi' : 'cc-rpi', +} + +release_files = { + '/win32' : 'win32/ClassiCube.exe', '/win64' : 'win64/ClassiCube.exe', + '/osx32' : 'osx32/ClassiCube.tar.gz', '/osx64' : 'osx64/ClassiCube.tar.gz', + '/mac32' : 'mac32/ClassiCube.tar.gz', '/mac64' : 'mac64/ClassiCube.tar.gz', + '/nix32' : 'nix32/ClassiCube.tar.gz', '/nix64' : 'nix64/ClassiCube.tar.gz', + '/rpi32' : 'rpi32/ClassiCube.tar.gz', +} + +def run_script(file): + args = ["sh", file] + su = subprocess.Popen(args) + return su.wait() + +class Handler(SimpleHTTPRequestHandler): + + def serve_script(self, file, msg): + ret = run_script(file) + self.send_response(200) + self.end_headers() + self.wfile.write(msg % ret) + + def do_GET(self): + if self.path in build_files: + self.serve_exe('client/src/' + build_files[self.path]) + elif self.path in release_files: + self.serve_exe('client/release/' + release_files[self.path]) + elif self.path == '/rebuild': + self.serve_script('build.sh', 'Rebuild client (%s)') + elif self.path == '/rebuild_android': + self.serve_script('build_android.sh', 'Rebuild android (%s)') + elif self.path == '/release': + self.serve_script('build_release.sh', 'Package release (%s)') + else: + self.send_error(404, "Unknown action") + return + + def serve_exe(self, path): + try: + f = open(path, 'rb') + fs = os.fstat(f.fileno()) + self.send_response(200) + self.send_header("Content-type", "application/octet-stream") + self.send_header("Content-Length", str(fs[6])) + self.end_headers() + self.copyfile(f, self.wfile) + f.close() + except IOError: + self.send_error(404, "File not found") + +httpd = HTTPServer(('', 80), Handler) +httpd.serve_forever() diff --git a/misc/buildbot_android.sh b/misc/buildbot_android.sh new file mode 100644 index 000000000..498b7cc0a --- /dev/null +++ b/misc/buildbot_android.sh @@ -0,0 +1,54 @@ +FLAGS="-fPIC -shared -s -O1 -fvisibility=hidden -rdynamic" +LIBS="-lGLESv2 -lEGL -lm -landroid -llog" +NDK_ROOT="/home/buildbot/android/android-ndk-r22/toolchains/llvm/prebuilt/linux-x86_64/bin" +TOOLS_ROOT="/home/buildbot/android/sdk/build-tools/26.0.0" +SDK_ROOT="/home/buildbot/android/sdk/platforms/android-26" + +cd /home/buildbot/client/src +$NDK_ROOT/armv7a-linux-androideabi16-clang *.c $FLAGS -march=armv5 $LIBS -o cc-droid-arm_16 +$NDK_ROOT/armv7a-linux-androideabi16-clang *.c $FLAGS $LIBS -o cc-droid-arm_32 +$NDK_ROOT/aarch64-linux-android21-clang *.c $FLAGS $LIBS -o cc-droid-arm_64 +$NDK_ROOT/i686-linux-android16-clang *.c $FLAGS $LIBS -o cc-droid-x86_32 +$NDK_ROOT/x86_64-linux-android21-clang *.c $FLAGS $LIBS -o cc-droid-x86_64 + +cd ../android/app/src/main +# remove old java temp files +rm -rf obj +mkdir obj +rm classes.dex + +# copy required native libraries +rm -rf lib +mkdir lib +mkdir lib/armeabi +mkdir lib/armeabi-v7a +mkdir lib/arm64-v8a +mkdir lib/x86 +mkdir lib/x86_64 +cp ~/client/src/cc-droid-arm_16 lib/armeabi/libclassicube.so +cp ~/client/src/cc-droid-arm_32 lib/armeabi-v7a/libclassicube.so +cp ~/client/src/cc-droid-arm_64 lib/arm64-v8a/libclassicube.so +cp ~/client/src/cc-droid-x86_32 lib/x86/libclassicube.so +cp ~/client/src/cc-droid-x86_64 lib/x86_64/libclassicube.so + +# The following commands are for manually building an .apk, see +# https://spin.atomicobject.com/2011/08/22/building-android-application-bundles-apks-by-hand/ +# https://github.com/cnlohr/rawdrawandroid/blob/master/Makefile +# https://stackoverflow.com/questions/41132753/how-can-i-build-an-android-apk-without-gradle-on-the-command-line +# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/Makefile +# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/CMakeLists.txt + +# compile interop java file into its multiple .class files +javac java/com/classicube/MainActivity.java -d ./obj -classpath $SDK_ROOT/android.jar +# compile the multiple .class files into one .dex file +$TOOLS_ROOT/dx --dex --output=obj/classes.dex ./obj +# create initial .apk with packaged version of resources +$TOOLS_ROOT/aapt package -f -M AndroidManifest.xml -S res -F obj/cc-unsigned.apk -I $SDK_ROOT/android.jar +# and add all the required files +cp obj/classes.dex classes.dex +$TOOLS_ROOT/aapt add -f obj/cc-unsigned.apk classes.dex lib/armeabi/libclassicube.so lib/armeabi-v7a/libclassicube.so lib/arm64-v8a/libclassicube.so lib/x86/libclassicube.so lib/x86_64/libclassicube.so +# sign the apk with debug key (https://stackoverflow.com/questions/16711233/) +cp obj/cc-unsigned.apk obj/cc-signed.apk +jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore debug.keystore -storepass android -keypass android obj/cc-signed.apk androiddebugkey +# create aligned .apk file +$TOOLS_ROOT/zipalign -f -v 4 obj/cc-signed.apk ~/client/src/cc.apk \ No newline at end of file diff --git a/misc/notify.py b/misc/notify.py index a4866e9b2..b2c02a9ca 100644 --- a/misc/notify.py +++ b/misc/notify.py @@ -17,22 +17,23 @@ def notify_webhook(body): "content" : body } r = requests.post(WEBHOOK_URL, json=webhook_data) - print("BuildNotify response: " + r.text) + print("Webhook response: " + r.text) except Exception as e: - print("BuildNotify failed: %s" % (e)) + print("Webhook failed: %s" % (e)) def build_exists(file): return os.path.exists('client/src/' + file) builds = { - 'Win32' : build_exists('cc-w32-d3d.exe'), - 'Win64' : build_exists('cc-w64-d3d.exe'), - 'Mac32' : build_exists('cc-osx32'), - 'Mac64' : build_exists('cc-osx64'), - 'Nix32' : build_exists('cc-nix32'), - 'Nix64' : build_exists('cc-nix64'), - 'Rpi' : build_exists('cc-rpi'), - 'Web' : build_exists('cc.js'), + 'Windows32' : build_exists('cc-w32-d3d.exe'), + 'Windows64' : build_exists('cc-w64-d3d.exe'), + 'macOS32' : build_exists('cc-osx32'), + 'macOS64' : build_exists('cc-osx64'), + 'Linux32' : build_exists('cc-nix32'), + 'Linux64' : build_exists('cc-nix64'), + 'RPi' : build_exists('cc-rpi'), + 'Web' : build_exists('cc.js'), + 'Android' : build_exists('cc.apk'), 'Win-ogl32' : build_exists('cc-w32-ogl.exe'), 'Win-ogl64' : build_exists('cc-w64-ogl.exe'), } @@ -48,17 +49,19 @@ def check_build(key): failed.append(key) else: if not builds[key_32] and not builds[key_64]: - failed.append(key + '32/64') + failed.append(key + ' 32/64') elif not builds[key_32]: - failed.append(key_32) + failed.append(key + ' 32') elif not builds[key_64]: - failed.append(key_64) + failed.append(key + ' 64') -check_build('Win') -check_build('Mac') -check_build('Nix') -check_build('Rpi') +check_build('Windows') +check_build('macOS') +check_build('Linux') +check_build('RPi') check_build('Web') +check_build('Android') +check_build('Win-ogl') if len(failed): - notify_webhook('<@%s>, failed to compile for: %s' % (TARGET_USER, ', '.join(failed))) \ No newline at end of file + notify_webhook('<@%s>, failed to compile for: %s' % (TARGET_USER, ', '.join(failed))) diff --git a/readme.md b/readme.md index 49d6d079e..f3855e248 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, OSX, Linux, BSD, Solaris, Haiku, and in a browser. +ClassiCube is a custom Minecraft Classic and ClassiCube client written in C that works on Windows, macOS, Linux, Android, BSD, Solaris, Haiku, and in a browser. **It is not affiliated with (or supported by) Mojang AB, Minecraft, or Microsoft in any way.** ![screenshot_n](http://i.imgur.com/FCiwl27.png) @@ -14,9 +14,10 @@ You can grab the latest stable binaries [from here](https://www.classicube.net/d It **does not** work with 'modern/premium' Minecraft servers. #### Requirements -* Windows: 2000 or later. (Windows 98 with KernelEx also *technically* works) -* macOS: macOS 10.5 or later. (Can be compiled to work with 10.3/10.4 though) +* Windows: 98 or later. +* macOS: 10.5 or later. (Can be compiled to work with 10.3/10.4 though) * Linux: libcurl and libopenal. +* Android: 2.3 or later. **Note:** When running from within VirtualBox, disable Mouse Integration, otherwise the camera will not work properly. diff --git a/src/Audio.c b/src/Audio.c index c8132b391..81b2d2ca0 100644 --- a/src/Audio.c +++ b/src/Audio.c @@ -17,6 +17,8 @@ #include "Window.h" #endif +#define QUOTE(x) #x +#define DefineDynFunc(sym) { QUOTE(sym), (void**)&_ ## sym } int Audio_SoundsVolume, Audio_MusicVolume; #if defined CC_BUILD_NOAUDIO @@ -129,20 +131,18 @@ static const cc_string alLib = String_FromConst("libopenal.so"); static const cc_string alLib = String_FromConst("libopenal.so.1"); #endif -#define QUOTE(x) #x -#define DefineALFunc(sym) { QUOTE(sym), (void**)&_ ## sym } static cc_bool LoadALFuncs(void) { static const struct DynamicLibSym funcs[18] = { - DefineALFunc(alcCreateContext), DefineALFunc(alcMakeContextCurrent), - DefineALFunc(alcDestroyContext), DefineALFunc(alcOpenDevice), - DefineALFunc(alcCloseDevice), DefineALFunc(alcGetError), + DefineDynFunc(alcCreateContext), DefineDynFunc(alcMakeContextCurrent), + DefineDynFunc(alcDestroyContext), DefineDynFunc(alcOpenDevice), + DefineDynFunc(alcCloseDevice), DefineDynFunc(alcGetError), - DefineALFunc(alGetError), DefineALFunc(alGenSources), - DefineALFunc(alDeleteSources), DefineALFunc(alGetSourcei), - DefineALFunc(alSourcePlay), DefineALFunc(alSourceStop), - DefineALFunc(alSourceQueueBuffers), DefineALFunc(alSourceUnqueueBuffers), - DefineALFunc(alGenBuffers), DefineALFunc(alDeleteBuffers), - DefineALFunc(alBufferData), DefineALFunc(alDistanceModel) + DefineDynFunc(alGetError), DefineDynFunc(alGenSources), + DefineDynFunc(alDeleteSources), DefineDynFunc(alGetSourcei), + DefineDynFunc(alSourcePlay), DefineDynFunc(alSourceStop), + DefineDynFunc(alSourceQueueBuffers), DefineDynFunc(alSourceUnqueueBuffers), + DefineDynFunc(alGenBuffers), DefineDynFunc(alDeleteBuffers), + DefineDynFunc(alBufferData), DefineDynFunc(alDistanceModel) }; void* lib = DynamicLib_Load2(&alLib); @@ -390,22 +390,51 @@ struct AudioContext { SLBufferQueueItf bqPlayerQueue; }; +static SLresult (SLAPIENTRY *_slCreateEngine)( + SLObjectItf *pEngine, + SLuint32 numOptions, + const SLEngineOption *pEngineOptions, + SLuint32 numInterfaces, + const SLInterfaceID *pInterfaceIds, + const SLboolean *pInterfaceRequired +); +static SLInterfaceID* _SL_IID_NULL; +static SLInterfaceID* _SL_IID_PLAY; +static SLInterfaceID* _SL_IID_ENGINE; +static SLInterfaceID* _SL_IID_BUFFERQUEUE; +static const cc_string slLib = String_FromConst("libOpenSLES.so"); + +static cc_bool LoadSLFuncs(void) { + static const struct DynamicLibSym funcs[5] = { + DefineDynFunc(slCreateEngine), DefineDynFunc(SL_IID_NULL), + DefineDynFunc(SL_IID_PLAY), DefineDynFunc(SL_IID_ENGINE), + DefineDynFunc(SL_IID_BUFFERQUEUE) + }; + + void* lib = DynamicLib_Load2(&slLib); + if (!lib) { Logger_DynamicLibWarn("loading", &slLib); return false; } + return DynamicLib_GetAll(lib, funcs, Array_Elems(funcs)); +} + static cc_bool Backend_Init(void) { + static const cc_string msg = String_FromConst("Failed to init OpenSLES. No audio will play."); SLInterfaceID ids[1]; SLboolean req[1]; SLresult res; if (slEngineObject) return true; - /* mixer doesn't use any effects */ - ids[0] = SL_IID_NULL; req[0] = SL_BOOLEAN_FALSE; + if (!LoadSLFuncs()) { Logger_WarnFunc(&msg); return false; } - res = slCreateEngine(&slEngineObject, 0, NULL, 0, NULL, NULL); + /* mixer doesn't use any effects */ + ids[0] = *_SL_IID_NULL; req[0] = SL_BOOLEAN_FALSE; + + res = _slCreateEngine(&slEngineObject, 0, NULL, 0, NULL, NULL); if (res) { Logger_SimpleWarn(res, "creating OpenSL ES engine"); return false; } res = (*slEngineObject)->Realize(slEngineObject, SL_BOOLEAN_FALSE); if (res) { Logger_SimpleWarn(res, "realising OpenSL ES engine"); return false; } - res = (*slEngineObject)->GetInterface(slEngineObject, SL_IID_ENGINE, &slEngineEngine); + res = (*slEngineObject)->GetInterface(slEngineObject, *_SL_IID_ENGINE, &slEngineEngine); if (res) { Logger_SimpleWarn(res, "initing OpenSL ES engine"); return false; } res = (*slEngineEngine)->CreateOutputMix(slEngineEngine, &slOutputObject, 1, ids, req); @@ -489,16 +518,16 @@ static cc_result Backend_SetFormat(struct AudioContext* ctx, struct AudioFormat* dst.pLocator = &output; dst.pFormat = NULL; - ids[0] = SL_IID_BUFFERQUEUE; req[0] = SL_BOOLEAN_TRUE; - ids[1] = SL_IID_PLAY; req[1] = SL_BOOLEAN_TRUE; + ids[0] = *_SL_IID_BUFFERQUEUE; req[0] = SL_BOOLEAN_TRUE; + ids[1] = *_SL_IID_PLAY; req[1] = SL_BOOLEAN_TRUE; res = (*slEngineEngine)->CreateAudioPlayer(slEngineEngine, &bqPlayerObject, &src, &dst, 2, ids, req); ctx->bqPlayerObject = bqPlayerObject; if (res) return res; - if ((res = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE))) return res; - if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &ctx->bqPlayerPlayer))) return res; - if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &ctx->bqPlayerQueue))) return res; + if ((res = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE))) return res; + if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, *_SL_IID_PLAY, &ctx->bqPlayerPlayer))) return res; + if ((res = (*bqPlayerObject)->GetInterface(bqPlayerObject, *_SL_IID_BUFFERQUEUE, &ctx->bqPlayerQueue))) return res; return (*ctx->bqPlayerQueue)->RegisterCallback(ctx->bqPlayerQueue, OnBufferFinished, ctx); } diff --git a/src/Constants.h b/src/Constants.h index cf3bd3df6..db44f942b 100644 --- a/src/Constants.h +++ b/src/Constants.h @@ -5,7 +5,7 @@ */ #define GAME_MAX_CMDARGS 5 -#define GAME_APP_VER "1.2.3" +#define GAME_APP_VER "1.2.4" #define GAME_API_VER 1 #if defined CC_BUILD_WEB @@ -13,14 +13,14 @@ #define GAME_APP_NAME "ClassiCube web" #define GAME_APP_TITLE "ClassiCube" #elif defined CC_BUILD_COCOA -#define GAME_APP_NAME "ClassiCube 1.2.3 alpha" -#define GAME_APP_TITLE "ClassiCube 1.2.3 alpha" +#define GAME_APP_NAME "ClassiCube 1.2.4 alpha" +#define GAME_APP_TITLE "ClassiCube 1.2.4 alpha" #elif defined CC_BUILD_ANDROID -#define GAME_APP_NAME "ClassiCube 1.2.3 android alpha" -#define GAME_APP_TITLE "ClassiCube 1.2.3 android alpha" +#define GAME_APP_NAME "ClassiCube 1.2.4 android alpha" +#define GAME_APP_TITLE "ClassiCube 1.2.4 android alpha" #else -#define GAME_APP_NAME "ClassiCube 1.2.3" -#define GAME_APP_TITLE "ClassiCube 1.2.3" +#define GAME_APP_NAME "ClassiCube 1.2.4" +#define GAME_APP_TITLE "ClassiCube 1.2.4" #endif /* Max number of characters strings can have. */ diff --git a/src/Drawer2D.c b/src/Drawer2D.c index c0bdda8fb..8487e1ee4 100644 --- a/src/Drawer2D.c +++ b/src/Drawer2D.c @@ -36,7 +36,7 @@ void DrawTextArgs_MakeEmpty(struct DrawTextArgs* args, struct FontDesc* font, cc *-----------------------------------------------------Font functions------------------------------------------------------* *#########################################################################################################################*/ static char defaultBuffer[STRING_SIZE]; -static cc_string font_candidates[11] = { +static cc_string font_candidates[12] = { String_FromArray(defaultBuffer), /* Filled in with user's default font */ String_FromConst("Arial"), /* preferred font on all platforms */ String_FromConst("Liberation Sans"), /* nice looking fallbacks for linux */ @@ -45,9 +45,10 @@ static cc_string font_candidates[11] = { String_FromConst("Cantarell"), String_FromConst("DejaVu Sans Book"), String_FromConst("Century Schoolbook L Roman"), /* commonly available on linux */ - String_FromConst("Slate For OnePlus"), /* android 10, some devices */ - String_FromConst("Roboto"), /* android (broken on some android 10 devices) */ - String_FromConst("Geneva") /* for ancient macOS versions */ + String_FromConst("Slate For OnePlus"), /* Android 10, some devices */ + String_FromConst("Roboto"), /* Android (broken on some Android 10 devices) */ + String_FromConst("Geneva"), /* for ancient macOS versions */ + String_FromConst("Droid Sans") /* for old Android versions */ }; void Drawer2D_SetDefaultFont(const cc_string* fontName) { diff --git a/src/Graphics.c b/src/Graphics.c index a611ec840..0203059b7 100644 --- a/src/Graphics.c +++ b/src/Graphics.c @@ -973,13 +973,18 @@ void Gfx_CalcOrthoMatrix(float width, float height, struct Matrix* matrix) { matrix->row4.Z = ORTHO_NEAR / (ORTHO_NEAR - ORTHO_FAR); } -void Gfx_CalcPerspectiveMatrix(float fov, float aspect, float zFar, struct Matrix* matrix) { +static float CalcZNear(float fov) { /* With reversed z depth, near Z plane can be much closer (with sufficient depth buffer precision) */ /* This reduces clipping with high FOV without sacrificing depth precision for faraway objects */ /* However for low FOV, don't reduce near Z in order to gain a bit more depth precision */ - float zNear = (depthBits < 24 || fov <= 70 * MATH_DEG2RAD) ? 0.05f : 0.001953125f; - Matrix_PerspectiveFieldOfView(matrix, fov, aspect, zNear, zFar); + if (depthBits < 24 || fov <= 70 * MATH_DEG2RAD) return 0.05f; + if (fov <= 100 * MATH_DEG2RAD) return 0.025f; + if (fov <= 150 * MATH_DEG2RAD) return 0.0125f; + return 0.00390625f; +} +void Gfx_CalcPerspectiveMatrix(float fov, float aspect, float zFar, struct Matrix* matrix) { + Matrix_PerspectiveFieldOfView(matrix, fov, aspect, CalcZNear(fov), zFar); /* Adjust the projection matrix to produce reversed Z values */ matrix->row3.Z = -matrix->row3.Z - 1.0f; matrix->row4.Z = -matrix->row4.Z; diff --git a/src/Input.c b/src/Input.c index 068519c0d..1ce1485b8 100644 --- a/src/Input.c +++ b/src/Input.c @@ -68,6 +68,18 @@ static cc_bool AnyBlockTouches(void) { return false; } +static void ClearTouches(void) { + int i; + for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0; + Pointers_Count = 0; +} + +void Input_SetTouchMode(cc_bool enabled) { + ClearTouches(); + Input_TouchMode = enabled; + Pointers_Count = enabled ? 0 : 1; +} + void Input_AddTouch(long id, int x, int y) { int i; for (i = 0; i < INPUT_MAX_POINTERS; i++) { @@ -215,6 +227,9 @@ void Input_SetPressed(int key) { Input_Pressed[key] = true; Event_RaiseInput(&InputEvents.Down, key, wasPressed); + if (key == 'C' && Key_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0); + if (key == 'V' && Key_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0); + /* don't allow multiple left mouse down events */ if (key != KEY_LMOUSE || wasPressed) return; Pointer_SetPressed(0, true); @@ -456,9 +471,9 @@ int Hotkeys_FindPartial(int key) { struct HotkeyData hk; int i, modifiers = 0; - if (Key_IsControlPressed()) modifiers |= HOTKEY_MOD_CTRL; - if (Key_IsShiftPressed()) modifiers |= HOTKEY_MOD_SHIFT; - if (Key_IsAltPressed()) modifiers |= HOTKEY_MOD_ALT; + if (Key_IsCtrlPressed()) modifiers |= HOTKEY_MOD_CTRL; + if (Key_IsShiftPressed()) modifiers |= HOTKEY_MOD_SHIFT; + if (Key_IsAltPressed()) modifiers |= HOTKEY_MOD_ALT; for (i = 0; i < HotkeysText.count; i++) { hk = HotkeysList[i]; @@ -806,7 +821,7 @@ cc_bool Input_HandleMouseWheel(float delta) { struct HacksComp* h; cc_bool hotbar; - hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed(); + hotbar = Key_IsAltPressed() || Key_IsCtrlPressed() || Key_IsShiftPressed(); if (!hotbar && Camera.Active->Zoom(delta)) return true; if (!KeyBind_IsPressed(KEYBIND_ZOOM_SCROLL)) return false; @@ -1089,9 +1104,7 @@ static void OnInit(void) { static void OnFree(void) { #ifdef CC_BUILD_TOUCH - int i; - for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0; - Pointers_Count = 0; + ClearTouches(); #endif } diff --git a/src/Input.h b/src/Input.h index 754066a8e..bc3faba82 100644 --- a/src/Input.h +++ b/src/Input.h @@ -44,22 +44,25 @@ enum InputButtons { /* NOTE: RMOUSE must be before MMOUSE for PlayerClick compatibility */ KEY_XBUTTON1, KEY_XBUTTON2, KEY_LMOUSE, KEY_RMOUSE, KEY_MMOUSE, - INPUT_COUNT + INPUT_COUNT, + + INPUT_CLIPBOARD_COPY = 1001, + INPUT_CLIPBOARD_PASTE = 1002 }; /* Simple names for each input button. */ extern const char* const Input_Names[INPUT_COUNT]; -#define Key_IsWinPressed() (Input_Pressed[KEY_LWIN] || Input_Pressed[KEY_RWIN]) -#define Key_IsAltPressed() (Input_Pressed[KEY_LALT] || Input_Pressed[KEY_RALT]) -#define Key_IsControlPressed() (Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL]) -#define Key_IsShiftPressed() (Input_Pressed[KEY_LSHIFT] || Input_Pressed[KEY_RSHIFT]) +#define Key_IsWinPressed() (Input_Pressed[KEY_LWIN] || Input_Pressed[KEY_RWIN]) +#define Key_IsAltPressed() (Input_Pressed[KEY_LALT] || Input_Pressed[KEY_RALT]) +#define Key_IsCtrlPressed() (Input_Pressed[KEY_LCTRL] || Input_Pressed[KEY_RCTRL]) +#define Key_IsShiftPressed() (Input_Pressed[KEY_LSHIFT] || Input_Pressed[KEY_RSHIFT]) #ifdef CC_BUILD_DARWIN /* macOS uses CMD instead of CTRL for clipboard and stuff */ #define Key_IsActionPressed() Key_IsWinPressed() #else -#define Key_IsActionPressed() Key_IsControlPressed() +#define Key_IsActionPressed() Key_IsCtrlPressed() #endif /* Pressed state of each input button. Use Input_Set to change. */ @@ -83,6 +86,7 @@ extern int Pointers_Count; extern cc_bool Input_TapPlace, Input_HoldPlace; /* Whether touch input is being used. */ extern cc_bool Input_TouchMode; +void Input_SetTouchMode(cc_bool enabled); void Input_AddTouch(long id, int x, int y); void Input_UpdateTouch(long id, int x, int y); diff --git a/src/LScreens.c b/src/LScreens.c index 4da3bc693..2c41cf0f7 100644 --- a/src/LScreens.c +++ b/src/LScreens.c @@ -1005,10 +1005,8 @@ static struct CheckResourcesScreen { static void CheckResourcesScreen_Yes(void* w, int idx) { FetchResourcesScreen_SetActive(); } static void CheckResourcesScreen_Next(void* w, int idx) { - static const cc_string optionsTxt = String_FromConst("options.txt"); Http_ClearPending(); - - if (File_Exists(&optionsTxt)) { + if (Options_LoadResult != ReturnCode_FileNotFound) { MainScreen_SetActive(); } else { ChooseModeScreen_SetActive(true); diff --git a/src/LWeb.c b/src/LWeb.c index 5aa92ce6b..de3b1782c 100644 --- a/src/LWeb.c +++ b/src/LWeb.c @@ -109,43 +109,47 @@ static void Json_ConsumeObject(struct JsonContext* ctx) { char keyBuffer[STRING_SIZE]; cc_string value, oldKey = ctx->curKey; int token; + ctx->depth++; ctx->OnNewObject(ctx); while (true) { token = Json_ConsumeToken(ctx); if (token == ',') continue; - if (token == '}') return; + if (token == '}') break; - if (token != '"') { ctx->failed = true; return; } + if (token != '"') { ctx->failed = true; break; } String_InitArray(ctx->curKey, keyBuffer); Json_ConsumeString(ctx, &ctx->curKey); token = Json_ConsumeToken(ctx); - if (token != ':') { ctx->failed = true; return; } + if (token != ':') { ctx->failed = true; break; } token = Json_ConsumeToken(ctx); - if (token == TOKEN_NONE) { ctx->failed = true; return; } + if (token == TOKEN_NONE) { ctx->failed = true; break; } value = Json_ConsumeValue(token, ctx); ctx->OnValue(ctx, &value); ctx->curKey = oldKey; } + ctx->depth--; } static void Json_ConsumeArray(struct JsonContext* ctx) { cc_string value; int token; + ctx->depth++; ctx->OnNewArray(ctx); while (true) { token = Json_ConsumeToken(ctx); if (token == ',') continue; - if (token == ']') return; + if (token == ']') break; - if (token == TOKEN_NONE) { ctx->failed = true; return; } + if (token == TOKEN_NONE) { ctx->failed = true; break; } value = Json_ConsumeValue(token, ctx); ctx->OnValue(ctx, &value); } + ctx->depth--; } static cc_string Json_ConsumeValue(int token, struct JsonContext* ctx) { @@ -169,6 +173,7 @@ void Json_Init(struct JsonContext* ctx, STRING_REF char* str, int len) { ctx->left = len; ctx->failed = false; ctx->curKey = String_Empty; + ctx->depth = 0; ctx->OnNewArray = Json_NullOnNew; ctx->OnNewObject = Json_NullOnNew; @@ -421,12 +426,19 @@ void FetchServerTask_Run(const cc_string* hash) { *#########################################################################################################################*/ struct FetchServersData FetchServersTask; static void FetchServersTask_Count(struct JsonContext* ctx) { + /* JSON is expected in this format: */ + /* { "servers" : (depth = 1) */ + /* [ (depth = 2) */ + /* { server1 }, (depth = 3) */ + /* { server2 }, (depth = 3) */ + /* ... */ + if (ctx->depth != 3) return; FetchServersTask.numServers++; } static void FetchServersTask_Next(struct JsonContext* ctx) { + if (ctx->depth != 3) return; curServer++; - if (curServer < FetchServersTask.servers) return; ServerInfo_Init(curServer); } @@ -440,8 +452,7 @@ static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) { FetchServersTask.servers = NULL; FetchServersTask.orders = NULL; - /* -1 because servers is surrounded by a { */ - FetchServersTask.numServers = -1; + FetchServersTask.numServers = 0; Json_Handle(data, len, NULL, NULL, FetchServersTask_Count); count = FetchServersTask.numServers; @@ -449,8 +460,7 @@ static void FetchServersTask_Handle(cc_uint8* data, cc_uint32 len) { FetchServersTask.servers = (struct ServerInfo*)Mem_Alloc(count, sizeof(struct ServerInfo), "servers list"); FetchServersTask.orders = (cc_uint16*)Mem_Alloc(count, 2, "servers order"); - /* -2 because servers is surrounded by a { */ - curServer = FetchServersTask.servers - 2; + curServer = FetchServersTask.servers - 1; Json_Handle(data, len, ServerInfo_Parse, NULL, FetchServersTask_Next); } diff --git a/src/LWeb.h b/src/LWeb.h index 57c271d8b..a70c0a08f 100644 --- a/src/LWeb.h +++ b/src/LWeb.h @@ -15,6 +15,7 @@ struct JsonContext { int left; /* Number of characters left to be inspected. */ cc_bool failed; /* Whether there was an error parsing the JSON. */ cc_string curKey; /* Key/Name of current member */ + int depth; /* Object/Array depth (e.g. { { { is depth 3 */ JsonOnNew OnNewArray; /* Invoked when start of an array is read. */ JsonOnNew OnNewObject; /* Invoked when start of an object is read. */ diff --git a/src/LWidgets.c b/src/LWidgets.c index 974db27f6..c916fb621 100644 --- a/src/LWidgets.c +++ b/src/LWidgets.c @@ -438,13 +438,16 @@ static void LInput_Unselect(void* widget, int idx) { Window_CloseKeyboard(); } -static void LInput_CopyFromClipboard(cc_string* text, void* widget) { - struct LInput* w = (struct LInput*)widget; - String_UNSAFE_TrimStart(text); - String_UNSAFE_TrimEnd(text); +static void LInput_CopyFromClipboard(struct LInput* w) { + cc_string text; char textBuffer[2048]; + String_InitArray(text, textBuffer); - if (w->ClipboardFilter) w->ClipboardFilter(text); - LInput_AppendString(w, text); + Clipboard_GetText(&text); + String_UNSAFE_TrimStart(&text); + String_UNSAFE_TrimEnd(&text); + + if (w->ClipboardFilter) w->ClipboardFilter(&text); + LInput_AppendString(w, &text); } static void LInput_KeyDown(void* widget, int key, cc_bool was) { @@ -453,10 +456,10 @@ static void LInput_KeyDown(void* widget, int key, cc_bool was) { LInput_Backspace(w); } else if (key == KEY_DELETE) { LInput_Delete(w); - } else if (key == 'C' && Key_IsActionPressed()) { + } else if (key == INPUT_CLIPBOARD_COPY) { if (w->text.length) Clipboard_SetText(&w->text); - } else if (key == 'V' && Key_IsActionPressed()) { - Clipboard_RequestText(LInput_CopyFromClipboard, w); + } else if (key == INPUT_CLIPBOARD_PASTE) { + LInput_CopyFromClipboard(w); } else if (key == KEY_ESCAPE) { LInput_Clear(w); } else if (key == KEY_LEFT) { diff --git a/src/Options.c b/src/Options.c index a7cf3ac26..20b011e43 100644 --- a/src/Options.c +++ b/src/Options.c @@ -9,6 +9,7 @@ struct StringsBuffer Options; static struct StringsBuffer changedOpts; +cc_result Options_LoadResult; void Options_Free(void) { StringsBuffer_Clear(&Options); @@ -35,8 +36,8 @@ static cc_bool Options_LoadFilter(const cc_string* entry) { void Options_Load(void) { /* Increase from max 512 to 2048 per entry */ StringsBuffer_SetLengthBits(&Options, 11); - EntryList_Load(&Options, "options-default.txt", '=', NULL); - EntryList_Load(&Options, "options.txt", '=', NULL); + Options_LoadResult = EntryList_Load(&Options, "options-default.txt", '=', NULL); + Options_LoadResult = EntryList_Load(&Options, "options.txt", '=', NULL); } void Options_Reload(void) { @@ -52,7 +53,7 @@ void Options_Reload(void) { StringsBuffer_Remove(&Options, i); } /* Load only options which have not changed */ - EntryList_Load(&Options, "options.txt", '=', Options_LoadFilter); + Options_LoadResult = EntryList_Load(&Options, "options.txt", '=', Options_LoadFilter); } static void SaveOptions(void) { diff --git a/src/Options.h b/src/Options.h index b99750c28..0b6f19a66 100644 --- a/src/Options.h +++ b/src/Options.h @@ -72,6 +72,7 @@ #define OPT_TOUCH_BUTTONS "gui-touchbuttons" #define OPT_TOUCH_SCALE "gui-touchscale" #define OPT_HTTP_ONLY "http-no-https" +#define OPT_RAW_INPUT "win-raw-input" #define LOPT_SESSION "launcher-session" #define LOPT_USERNAME "launcher-cc-username" @@ -87,6 +88,7 @@ struct StringsBuffer; extern struct StringsBuffer Options; +extern cc_result Options_LoadResult; /* Frees any memory allocated in storing options. */ void Options_Free(void); diff --git a/src/Platform.c b/src/Platform.c index d423c6c88..00232cc85 100644 --- a/src/Platform.c +++ b/src/Platform.c @@ -357,48 +357,68 @@ int File_Exists(const cc_string* path) { return attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY); } +static cc_result Directory_EnumCore(const cc_string* dirPath, const cc_string* file, DWORD attribs, + void* obj, Directory_EnumCallback callback) { + cc_string path; char pathBuffer[MAX_PATH + 10]; + /* ignore . and .. entry */ + if (file->length == 1 && file->buffer[0] == '.') return 0; + if (file->length == 2 && file->buffer[0] == '.' && file->buffer[1] == '.') return 0; + + String_InitArray(path, pathBuffer); + String_Format2(&path, "%s/%s", dirPath, file); + + if (attribs & FILE_ATTRIBUTE_DIRECTORY) return Directory_Enum(&path, obj, callback); + callback(&path, obj); + return 0; +} + cc_result Directory_Enum(const cc_string* dirPath, void* obj, Directory_EnumCallback callback) { cc_string path; char pathBuffer[MAX_PATH + 10]; WCHAR str[NATIVE_STR_LEN]; - WCHAR* src; - WIN32_FIND_DATAW entry; + WIN32_FIND_DATAW eW; + WIN32_FIND_DATAA eA; + int i, ansi = false; HANDLE find; cc_result res; - int i; /* Need to append \* to search for files in directory */ String_InitArray(path, pathBuffer); String_Format1(&path, "%s\\*", dirPath); Platform_EncodeUtf16(str, &path); - find = FindFirstFileW(str, &entry); - if (find == INVALID_HANDLE_VALUE) return GetLastError(); + find = FindFirstFileW(str, &eW); + if (!find || find == INVALID_HANDLE_VALUE) { + if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; + ansi = true; - do { - path.length = 0; - String_Format1(&path, "%s/", dirPath); + /* Windows 9x does not support W API functions */ + Platform_Utf16ToAnsi(str); + find = FindFirstFileA((LPCSTR)str, &eA); + if (find == INVALID_HANDLE_VALUE) return GetLastError(); + } - /* ignore . and .. entry */ - src = entry.cFileName; - if (src[0] == '.' && src[1] == '\0') continue; - if (src[0] == '.' && src[1] == '.' && src[2] == '\0') continue; - - for (i = 0; i < MAX_PATH && src[i]; i++) { - /* TODO: UTF16 to codepoint conversion */ - String_Append(&path, Convert_CodepointToCP437(src[i])); - } - - if (entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - res = Directory_Enum(&path, obj, callback); - if (res) { FindClose(find); return res; } - } else { - callback(&path, obj); - } - } while (FindNextFileW(find, &entry)); + if (ansi) { + do { + path.length = 0; + for (i = 0; i < MAX_PATH && eA.cFileName[i]; i++) { + String_Append(&path, Convert_CodepointToCP437(eA.cFileName[i])); + } + if ((res = Directory_EnumCore(dirPath, &path, eA.dwFileAttributes, obj, callback))) return res; + } while (FindNextFileA(find, &eA)); + } else { + do { + path.length = 0; + for (i = 0; i < MAX_PATH && eW.cFileName[i]; i++) { + /* TODO: UTF16 to codepoint conversion */ + String_Append(&path, Convert_CodepointToCP437(eW.cFileName[i])); + } + if ((res = Directory_EnumCore(dirPath, &path, eW.dwFileAttributes, obj, callback))) return res; + } while (FindNextFileW(find, &eW)); + } res = GetLastError(); /* return code from FindNextFile */ FindClose(find); - return res == ERROR_NO_MORE_FILES ? 0 : GetLastError(); + return res == ERROR_NO_MORE_FILES ? 0 : res; } static cc_result DoFile(cc_file* file, const cc_string* path, DWORD access, DWORD createMode) { @@ -1021,19 +1041,40 @@ cc_result Socket_Poll(cc_socket s, int mode, cc_bool* success) { *-----------------------------------------------------Process/Module------------------------------------------------------* *#########################################################################################################################*/ #if defined CC_BUILD_WIN -static cc_result Process_RawStart(WCHAR* path, WCHAR* args) { - STARTUPINFOW si = { 0 }; +static cc_result Process_RawGetExePath(WCHAR* path, int* len) { + *len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN); + return *len ? 0 : GetLastError(); +} + +cc_result Process_StartGame(const cc_string* args) { + WCHAR path[NATIVE_STR_LEN + 1], raw[NATIVE_STR_LEN]; + cc_string argv; char argvBuffer[NATIVE_STR_LEN]; + STARTUPINFOW si = { 0 }; PROCESS_INFORMATION pi = { 0 }; cc_result res; + int len; + + Process_RawGetExePath(path, &len); + path[len] = '\0'; si.cb = sizeof(STARTUPINFOW); + + String_InitArray(argv, argvBuffer); + /* Game doesn't actually care about argv[0] */ + String_Format1(&argv, "cc %s", args); + String_UNSAFE_TrimEnd(&argv); + Platform_EncodeUtf16(raw, &argv); - if (CreateProcessW(path, args, NULL, NULL, + if (CreateProcessW(path, raw, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) goto success; - //if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; + if ((res = GetLastError()) != ERROR_CALL_NOT_IMPLEMENTED) return res; - //Platform_Utf16ToAnsi(path); - //if (CreateProcessA((LPCSTR)path, args, NULL, NULL, - // false, 0, NULL, NULL, &si, &pi)) goto success; + /* Windows 9x does not support W API functions */ + len = GetModuleFileNameA(NULL, (LPSTR)path, NATIVE_STR_LEN); + ((char*)path)[len] = '\0'; + Platform_Utf16ToAnsi(raw); + + if (CreateProcessA((LPCSTR)path, (LPSTR)raw, NULL, NULL, + false, 0, NULL, NULL, &si, &pi)) goto success; return GetLastError(); success: @@ -1043,27 +1084,7 @@ success: return 0; } -static cc_result Process_RawGetExePath(WCHAR* path, int* len) { - *len = GetModuleFileNameW(NULL, path, NATIVE_STR_LEN); - return *len ? 0 : GetLastError(); -} - -cc_result Process_StartGame(const cc_string* args) { - cc_string argv; char argvBuffer[NATIVE_STR_LEN]; - WCHAR raw[NATIVE_STR_LEN], path[NATIVE_STR_LEN + 1]; - int len; - - cc_result res = Process_RawGetExePath(path, &len); - if (res) return res; - path[len] = '\0'; - - String_InitArray(argv, argvBuffer); - String_Format1(&argv, "ClassiCube.exe %s", args); - Platform_EncodeUtf16(raw, &argv); - return Process_RawStart(path, raw); -} void Process_Exit(cc_result code) { ExitProcess(code); } - void Process_StartOpen(const cc_string* args) { WCHAR str[NATIVE_STR_LEN]; Platform_EncodeUtf16(str, args); @@ -1272,7 +1293,6 @@ cc_bool Updater_Clean(void) { cc_result Updater_Start(const char** action) { WCHAR path[NATIVE_STR_LEN + 1]; - WCHAR args[2] = { 'a', '\0' }; /* don't actually care about arguments */ cc_result res; int len = 0; @@ -1286,7 +1306,7 @@ cc_result Updater_Start(const char** action) { if (!MoveFileExW(UPDATE_SRC, path, MOVEFILE_REPLACE_EXISTING)) return GetLastError(); *action = "Restarting game"; - return Process_RawStart(path, args); + return Process_StartGame(&String_Empty); } cc_result Updater_GetBuildTime(cc_uint64* timestamp) { @@ -1343,10 +1363,7 @@ cc_result Updater_GetBuildTime(cc_uint64* t) { return ERR_NOT_SUPPORTED; } cc_result Updater_GetBuildTime(cc_uint64* t) { JNIEnv* env; JavaGetCurrentEnv(env); - - /* https://developer.android.com/reference/java/io/File#lastModified() */ - /* lastModified is returned in milliseconds */ - *t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL) / 1000; + *t = JavaCallLong(env, "getApkUpdateTime", "()J", NULL); return 0; } #endif @@ -1565,7 +1582,7 @@ void Platform_Utf16ToAnsi(void* data) { WCHAR* src = (WCHAR*)data; char* dst = (char*)data; - while (*src) { *dst++ = *src++; } + while (*src) { *dst++ = (char)(*src++); } *dst = '\0'; } diff --git a/src/Protocol.c b/src/Protocol.c index 8d2646097..09c27a37e 100644 --- a/src/Protocol.c +++ b/src/Protocol.c @@ -737,9 +737,12 @@ static void Classic_Reset(void) { } static void Classic_Tick(void) { - struct Entity* p = &LocalPlayer_Instance.Base; + struct LocalPlayer* p = &LocalPlayer_Instance; + struct Entity* e = &LocalPlayer_Instance.Base; if (!classic_receivedFirstPos) return; - Classic_WritePosition(p->Position, p->Yaw, p->Pitch); + /* Report end position of each physics tick, rather than current position */ + /* (otherwise can miss landing on a block then jumping off of it again) */ + Classic_WritePosition(p->Interp.Next.Pos, e->Yaw, e->Pitch); } diff --git a/src/Screens.c b/src/Screens.c index c07654eb6..5bc1479e7 100644 --- a/src/Screens.c +++ b/src/Screens.c @@ -1448,7 +1448,7 @@ static int InventoryScreen_PointerDown(void* screen, int id, int x, int y) { handled = Elem_HandlesPointerDown(table, id, x, y); if (!handled || table->pendingClose) { - hotbar = Key_IsControlPressed() || Key_IsShiftPressed(); + hotbar = Key_IsCtrlPressed() || Key_IsShiftPressed(); if (!hotbar) Gui_Remove((struct Screen*)s); } return TOUCH_TYPE_GUI; @@ -1467,7 +1467,7 @@ static int InventoryScreen_PointerMove(void* screen, int id, int x, int y) { static int InventoryScreen_MouseScroll(void* screen, float delta) { struct InventoryScreen* s = (struct InventoryScreen*)screen; - cc_bool hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed(); + cc_bool hotbar = Key_IsAltPressed() || Key_IsCtrlPressed() || Key_IsShiftPressed(); if (hotbar) return false; return Elem_HandlesMouseScroll(&s->table, delta); } diff --git a/src/Utils.c b/src/Utils.c index 2cd71f674..aa70e2980 100644 --- a/src/Utils.c +++ b/src/Utils.c @@ -222,7 +222,7 @@ int Convert_FromBase64(const char* src, int len, cc_uint8* dst) { /*########################################################################################################################* *--------------------------------------------------------EntryList--------------------------------------------------------* *#########################################################################################################################*/ -void EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter) { +cc_result EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter) { cc_string entry; char entryBuffer[1024]; cc_string path; cc_string key, value; @@ -236,8 +236,8 @@ void EntryList_Load(struct StringsBuffer* list, const char* file, char separator maxLen = list->_lenMask ? list->_lenMask : STRINGSBUFFER_DEF_LEN_MASK; res = Stream_OpenFile(&stream, &path); - if (res == ReturnCode_FileNotFound) return; - if (res) { Logger_SysWarn2(res, "opening", &path); return; } + if (res == ReturnCode_FileNotFound) return res; + if (res) { Logger_SysWarn2(res, "opening", &path); return res; } /* ReadLine reads single byte at a time */ Stream_ReadonlyBuffered(&buffered, &stream, buffer, sizeof(buffer)); @@ -274,10 +274,11 @@ void EntryList_Load(struct StringsBuffer* list, const char* file, char separator res = stream.Close(&stream); if (res) { Logger_SysWarn2(res, "closing", &path); } + return res; } -void EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file) { - EntryList_Load(list, file, '\0', NULL); +cc_result EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file) { + return EntryList_Load(list, file, '\0', NULL); } void EntryList_Save(struct StringsBuffer* list, const char* file) { diff --git a/src/Utils.h b/src/Utils.h index b76f96df6..5b6aef562 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -59,10 +59,10 @@ typedef cc_bool (*EntryList_Filter)(const cc_string* entry); /* Loads the entries from disc. */ /* NOTE: If separator is \0, does NOT check for duplicate keys when loading. */ /* filter can be used to optionally skip loading some entries from the file. */ -CC_NOINLINE void EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter); +CC_NOINLINE cc_result EntryList_Load(struct StringsBuffer* list, const char* file, char separator, EntryList_Filter filter); /* Shortcut for EntryList_Load with separator of \0 and filter of NULL */ /* NOTE: Does NOT check for duplicate keys */ -CC_NOINLINE void EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file); +CC_NOINLINE cc_result EntryList_UNSAFE_Load(struct StringsBuffer* list, const char* file); /* Saves the entries in the given list to disc. */ CC_NOINLINE void EntryList_Save(struct StringsBuffer* list, const char* file); /* Removes the entry whose key caselessly equals the given key. */ diff --git a/src/Widgets.c b/src/Widgets.c index b34e41349..910830f65 100644 --- a/src/Widgets.c +++ b/src/Widgets.c @@ -1216,18 +1216,20 @@ static void InputWidget_EndKey(struct InputWidget* w) { InputWidget_UpdateCaret(w); } -static void InputWidget_CopyFromClipboard(cc_string* text, void* w) { - InputWidget_AppendText((struct InputWidget*)w, text); +static void InputWidget_CopyFromClipboard(struct InputWidget* w) { + cc_string text; char textBuffer[2048]; + String_InitArray(text, textBuffer); + + Clipboard_GetText(&text); + InputWidget_AppendText(w, &text); } static cc_bool InputWidget_OtherKey(struct InputWidget* w, int key) { int maxChars = w->GetMaxLines() * INPUTWIDGET_LEN; - if (!Key_IsActionPressed()) return false; - - if (key == 'V' && w->text.length < maxChars) { - Clipboard_RequestText(InputWidget_CopyFromClipboard, w); + if (key == INPUT_CLIPBOARD_PASTE && w->text.length < maxChars) { + InputWidget_CopyFromClipboard(w); return true; - } else if (key == 'C') { + } else if (key == INPUT_CLIPBOARD_COPY) { if (!w->text.length) return true; Clipboard_SetText(&w->text); return true; diff --git a/src/Window.c b/src/Window.c index bc354e2c7..3c75d984d 100644 --- a/src/Window.c +++ b/src/Window.c @@ -14,17 +14,9 @@ struct _DisplayData DisplayInfo; struct _WinData WindowInfo; int Display_ScaleX(int x) { return (int)(x * DisplayInfo.ScaleX); } -int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); } - -#ifndef CC_BUILD_WEB -void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) { - cc_string text; char textBuffer[2048]; - String_InitArray(text, textBuffer); - - Clipboard_GetText(&text); - callback(&text, obj); -} -#endif +int Display_ScaleY(int y) { return (int)(y * DisplayInfo.ScaleY); } +#define Display_CentreX(width) (DisplayInfo.X + (DisplayInfo.Width - width) / 2) +#define Display_CentreY(height) (DisplayInfo.Y + (DisplayInfo.Height - height) / 2) #if defined CC_BUILD_IOS /* iOS implements these functions in external interop_ios.m file */ @@ -982,7 +974,9 @@ static void InitRawMouse(void) { _getRawInputData = (FUNC_GetRawInputData) DynamicLib_Get2(lib, "GetRawInputData"); rawMouseSupported = _registerRawInput && _getRawInputData; } - if (!rawMouseSupported) { Platform_LogConst("Raw input unsupported!"); return; } + + rawMouseSupported &= Options_GetBool(OPT_RAW_INPUT, true); + if (!rawMouseSupported) { Platform_LogConst("## Raw input unsupported!"); return; } rid.usUsagePage = 1; /* HID_USAGE_PAGE_GENERIC; */ rid.usUsage = 2; /* HID_USAGE_GENERIC_MOUSE; */ @@ -2741,8 +2735,8 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) { /*########################################################################################################################* *-------------------------------------------------------Cocoa window------------------------------------------------------* *#########################################################################################################################*/ -#elif defined CC_BUILD_COCOA -/* NOTE: Mostly implemented in interop_cocoa.m */ +#elif defined CC_BUILD_COCOA +/* NOTE: Mostly implemented in interop_cocoa.m */ #endif @@ -2920,7 +2914,7 @@ static const char* OnBeforeUnload(int type, const void* ev, void *data) { return NULL; } -static int MapNativeKey(int k) { +static int MapNativeKey(int k, int l) { if (k >= '0' && k <= '9') return k; if (k >= 'A' && k <= 'Z') return k; if (k >= DOM_VK_F1 && k <= DOM_VK_F24) { return KEY_F1 + (k - DOM_VK_F1); } @@ -2929,10 +2923,10 @@ static int MapNativeKey(int k) { switch (k) { case DOM_VK_BACK_SPACE: return KEY_BACKSPACE; case DOM_VK_TAB: return KEY_TAB; - case DOM_VK_RETURN: return KEY_ENTER; - case DOM_VK_SHIFT: return KEY_LSHIFT; - case DOM_VK_CONTROL: return KEY_LCTRL; - case DOM_VK_ALT: return KEY_LALT; + case DOM_VK_RETURN: return l == DOM_KEY_LOCATION_NUMPAD ? KEY_KP_ENTER : KEY_ENTER; + case DOM_VK_SHIFT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RSHIFT : KEY_LSHIFT; + case DOM_VK_CONTROL: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RCTRL : KEY_LCTRL; + case DOM_VK_ALT: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RALT : KEY_LALT; case DOM_VK_PAUSE: return KEY_PAUSE; case DOM_VK_CAPS_LOCK: return KEY_CAPSLOCK; case DOM_VK_ESCAPE: return KEY_ESCAPE; @@ -2952,7 +2946,7 @@ static int MapNativeKey(int k) { case DOM_VK_SEMICOLON: return KEY_SEMICOLON; case DOM_VK_EQUALS: return KEY_EQUALS; - case DOM_VK_WIN: return KEY_LWIN; + case DOM_VK_WIN: return l == DOM_KEY_LOCATION_RIGHT ? KEY_RWIN : KEY_LWIN; case DOM_VK_MULTIPLY: return KEY_KP_MULTIPLY; case DOM_VK_ADD: return KEY_KP_PLUS; case DOM_VK_SUBTRACT: return KEY_KP_MINUS; @@ -2979,34 +2973,21 @@ static int MapNativeKey(int k) { return KEY_NONE; } -static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) { - int key = MapNativeKey(ev->keyCode); - - if (ev->location == DOM_KEY_LOCATION_RIGHT) { - switch (key) { - case KEY_LALT: key = KEY_RALT; break; - case KEY_LCTRL: key = KEY_RCTRL; break; - case KEY_LSHIFT: key = KEY_RSHIFT; break; - case KEY_LWIN: key = KEY_RWIN; break; - } - } - else if (ev->location == DOM_KEY_LOCATION_NUMPAD) { - switch (key) { - case KEY_ENTER: key = KEY_KP_ENTER; break; - } - } - - if (key) Input_Set(key, type == EMSCRIPTEN_EVENT_KEYDOWN); +static EM_BOOL OnKeyDown(int type, const EmscriptenKeyboardEvent* ev, void* data) { + int key = MapNativeKey(ev->keyCode, ev->location); + /* iOS safari still sends backspace key events, don't intercept those */ + if (key == KEY_BACKSPACE && Input_TouchMode && keyboardOpen) return false; + + if (key) Input_SetPressed(key); DeferredEnableRawMouse(); - if (!key) return false; - /* KeyUp always intercepted */ - if (type != EMSCRIPTEN_EVENT_KEYDOWN) return true; /* If holding down Ctrl or Alt, keys aren't going to generate a KeyPress event anyways. */ /* This intercepts Ctrl+S etc. Ctrl+C and Ctrl+V are not intercepted for clipboard. */ - if (Key_IsAltPressed() || Key_IsWinPressed()) return true; - if (Key_IsControlPressed() && key != 'C' && key != 'V') return true; + /* NOTE: macOS uses Win (Command) key instead of Ctrl, have to account for that too */ + if (Key_IsAltPressed()) return true; + if (Key_IsWinPressed()) return key != 'C' && key != 'V'; + if (Key_IsCtrlPressed()) return key != 'C' && key != 'V'; /* Space needs special handling, as intercepting this prevents the ' ' key press event */ /* But on Safari, space scrolls the page - so need to intercept when keyboard is NOT open */ @@ -3019,6 +3000,13 @@ static EM_BOOL OnKey(int type, const EmscriptenKeyboardEvent* ev, void* data) { (key >= KEY_INSERT && key <= KEY_MENU) || (key >= KEY_ENTER && key <= KEY_NUMLOCK); } +static EM_BOOL OnKeyUp(int type, const EmscriptenKeyboardEvent* ev, void* data) { + int key = MapNativeKey(ev->keyCode, ev->location); + if (key) Input_SetReleased(key); + DeferredEnableRawMouse(); + return key != KEY_NONE; +} + static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* data) { char keyChar; DeferredEnableRawMouse(); @@ -3032,6 +3020,10 @@ static EM_BOOL OnKeyPress(int type, const EmscriptenKeyboardEvent* ev, void* dat /* have these intercepted key presses in its text buffer) */ if (Input_TouchMode && keyboardOpen) return false; + /* Safari on macOS still sends a keypress event, which must not be cancelled */ + /* (otherwise copy/paste doesn't work, as it uses Win+C / Win+V) */ + if (ev->metaKey) return false; + if (Convert_TryCodepointToCP437(ev->charCode, &keyChar)) { Event_RaiseInt(&InputEvents.Press, keyChar); } @@ -3056,8 +3048,8 @@ static void HookEvents(void) { emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnResize); emscripten_set_beforeunload_callback( NULL, OnBeforeUnload); - emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKey); - emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKey); + emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyDown); + emscripten_set_keyup_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyUp); emscripten_set_keypress_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnKeyPress); emscripten_set_touchstart_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 0, OnTouchStart); @@ -3101,19 +3093,24 @@ void Window_Init(void) { EM_ASM(window.addEventListener('copy', function(e) { if (window.getSelection && window.getSelection().toString()) return; - if (window.cc_copyText) { - if (e.clipboardData) { e.clipboardData.setData('text/plain', window.cc_copyText); } + ccall('Window_RequestClipboardText', 'void'); + if (!window.cc_copyText) return; + + if (e.clipboardData) { + e.clipboardData.setData('text/plain', window.cc_copyText); e.preventDefault(); - window.cc_copyText = null; - } + } + window.cc_copyText = null; }); ); - /* Paste text (window.clipboardData is handled in Clipboard_RequestText instead) */ + /* Paste text (window.clipboardData is handled in Clipboard_GetText instead) */ EM_ASM(window.addEventListener('paste', function(e) { - var contents = e.clipboardData ? e.clipboardData.getData('text/plain') : ""; - ccall('Window_GotClipboardText', 'void', ['string'], [contents]); + if (e.clipboardData) { + var contents = e.clipboardData.getData('text/plain'); + ccall('Window_GotClipboardText', 'void', ['string'], [contents]); + } }); ); @@ -3124,8 +3121,7 @@ void Window_Init(void) { return /iPhone|iPad|iPod/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints && navigator.maxTouchPoints > 2); }); - Input_TouchMode = is_ios || droid; - Pointers_Count = Input_TouchMode ? 0 : 1; + Input_SetTouchMode(is_ios || droid); /* iOS shifts the whole webpage up when opening chat, which causes problems */ /* as the chat/send butons are positioned at the top of the canvas - they */ @@ -3169,20 +3165,33 @@ void Window_SetTitle(const cc_string* title) { EM_ASM_({ document.title = UTF8ToString($0); }, str); } -static RequestClipboardCallback clipboard_func; -static void* clipboard_obj; - -EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) { - cc_string str; char strBuffer[512]; - if (!clipboard_func) return; - - String_InitArray(str, strBuffer); - String_AppendUtf8(&str, src, String_CalcLen(src, 2048)); - clipboard_func(&str, clipboard_obj); - clipboard_func = NULL; +static char pasteBuffer[512]; +static cc_string pasteStr; +EMSCRIPTEN_KEEPALIVE void Window_RequestClipboardText(void) { + Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0); } -void Clipboard_GetText(cc_string* value) { } +EMSCRIPTEN_KEEPALIVE void Window_StoreClipboardText(char* src) { + String_InitArray(pasteStr, pasteBuffer); + String_AppendUtf8(&pasteStr, src, String_CalcLen(src, 2048)); +} + +EMSCRIPTEN_KEEPALIVE void Window_GotClipboardText(char* src) { + Window_StoreClipboardText(src); + Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0); +} + +void Clipboard_GetText(cc_string* value) { + /* For IE11, use window.clipboardData to get the clipboard */ + EM_ASM_({ + if (window.clipboardData) { + var contents = window.clipboardData.getData('Text'); + ccall('Window_StoreClipboardText', 'void', ['string'], [contents]); + } + }); + String_Copy(value, &pasteStr); + pasteStr.length = 0; +} void Clipboard_SetText(const cc_string* value) { char str[NATIVE_STR_LEN]; Platform_EncodeUtf8(str, value); @@ -3199,18 +3208,6 @@ void Clipboard_SetText(const cc_string* value) { }, str); } -void Clipboard_RequestText(RequestClipboardCallback callback, void* obj) { - clipboard_func = callback; - clipboard_obj = obj; - - /* For IE11, use window.clipboardData to get the clipboard */ - EM_ASM_({ - if (!window.clipboardData) return; - var contents = window.clipboardData.getData('Text'); - ccall('Window_GotClipboardText', 'void', ['string'], [contents]); - }); -} - void Window_Show(void) { } int Window_GetWindowState(void) { @@ -3672,7 +3669,8 @@ void Window_Init(void) { JavaRegisterNatives(env, methods); WindowInfo.SoftKeyboard = SOFT_KEYBOARD_RESIZE; - Input_TouchMode = true; + Input_SetTouchMode(true); + DisplayInfo.Depth = 32; DisplayInfo.ScaleX = JavaCallFloat(env, "getDpiX", "()F", NULL); DisplayInfo.ScaleY = JavaCallFloat(env, "getDpiY", "()F", NULL); @@ -3820,13 +3818,13 @@ void Window_FreeFramebuffer(struct Bitmap* bmp) { Mem_Free(bmp->scan0); } -void Window_OpenKeyboard(const struct OpenKeyboardArgs* args) { +void Window_OpenKeyboard(const struct OpenKeyboardArgs* kArgs) { JNIEnv* env; jvalue args[2]; JavaGetCurrentEnv(env); - args[0].l = JavaMakeString(env, args->text); - args[1].i = args->type; + args[0].l = JavaMakeString(env, kArgs->text); + args[1].i = kArgs->type; JavaCallVoid(env, "openKeyboard", "(Ljava/lang/String;I)V", args); (*env)->DeleteLocalRef(env, args[0].l); } @@ -4214,12 +4212,11 @@ static XVisualInfo GLContext_SelectVisual(void) { InitGraphicsMode(&mode); GetAttribs(&mode, attribs, GLCONTEXT_DEFAULT_DEPTH); - if (!glXQueryVersion(win_display, &major, &minor)) { - Logger_Abort("glXQueryVersion failed"); - } screen = DefaultScreen(win_display); - if (major >= 1 && minor >= 3) { + if (!glXQueryVersion(win_display, &major, &minor)) { + Platform_LogConst("glXQueryVersion failed"); + } else if (major >= 1 && minor >= 3) { /* ChooseFBConfig returns an array of GLXFBConfig opaque structures */ fbconfigs = glXChooseFBConfig(win_display, screen, attribs, &fbcount); if (fbconfigs && fbcount) { diff --git a/src/Window.h b/src/Window.h index 971a9e7a9..1d6da05c8 100644 --- a/src/Window.h +++ b/src/Window.h @@ -84,15 +84,14 @@ void Window_Create(int width, int height); /* Sets the text of the titlebar above the window. */ CC_API void Window_SetTitle(const cc_string* title); -typedef void (*RequestClipboardCallback)(cc_string* value, void* obj); /* Gets the text currently on the clipboard. */ +/* NOTE: On most platforms this function can be called at any time. */ +/* In web backend, can only be called during INPUT_CLIPBOARD_PASTE event. */ CC_API void Clipboard_GetText(cc_string* value); /* Sets the text currently on the clipboard. */ +/* NOTE: On most platforms this function can be called at any time. */ +/* In web backend, can only be called during INPUT_CLIPBOARD_COPY event. */ CC_API void Clipboard_SetText(const cc_string* value); -/* Calls a callback function when text is retrieved from the clipboard. */ -/* NOTE: On most platforms this just calls Clipboard_GetText. */ -/* With emscripten however, the callback is instead called when a 'paste' event arrives. */ -void Clipboard_RequestText(RequestClipboardCallback callback, void* obj); /* Makes the window visible and focussed on screen. */ void Window_Show(void);