From 273d2901a5936c5012b5a3826c9c04b33ffd60fa Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Mon, 7 Nov 2022 20:28:28 +1100 Subject: [PATCH 01/16] BSD: Add register defines for ARM/PPC/MIPS in Logger.c, completely untested though Also add register defines for SPARC for Solaris --- misc/buildbot.sh | 2 - src/Logger.c | 110 +++++++++++++++++++++++++++++++++++++++++++---- src/Window_X11.c | 2 +- 3 files changed, 103 insertions(+), 11 deletions(-) diff --git a/misc/buildbot.sh b/misc/buildbot.sh index 6e2569c68..9f8d05bfe 100644 --- a/misc/buildbot.sh +++ b/misc/buildbot.sh @@ -125,7 +125,6 @@ RPI64_CC=~/rpi64/cross-pi-gcc-9.4.0-64/bin/aarch64-linux-gnu-gcc build_rpi32() { echo "Building rpi32.." - cp $ROOT_DIR/misc/CCIcon_X11 $ROOT_DIR/src/CCIcon_X11 rm cc-rpi $RPI32_CC *.c $ALL_FLAGS $RPI_FLAGS -I ~/rpi/include -L ~/rpi/lib -DCC_COMMIT_SHA=\"$LATEST\" -o cc-rpi -lGLESv2 -lEGL -lX11 -lXi -lm -lpthread -ldl -lrt -Wl,-rpath-link ~/rpi/lib if [ $? -ne 0 ]; then echo "Failed to compile Raspberry Pi 32 bit" >> "$ERRS_FILE"; fi @@ -133,7 +132,6 @@ build_rpi32() { build_rpi64() { echo "Building rpi64.." - cp $ROOT_DIR/misc/CCIcon_X11 $ROOT_DIR/src/CCIcon_X11 rm cc-rpi64 $RPI64_CC *.c $ALL_FLAGS $RPI_FLAGS -DCC_COMMIT_SHA=\"$LATEST\" -o cc-rpi64 -lGLESv2 -lEGL -lX11 -lXi -lm -lpthread -ldl if [ $? -ne 0 ]; then echo "Failed to compile Raspberry Pi 64 bit" >> "$ERRS_FILE"; fi diff --git a/src/Logger.c b/src/Logger.c index d238fb3bd..29fae7d9a 100644 --- a/src/Logger.c +++ b/src/Logger.c @@ -590,22 +590,24 @@ static void PrintRegisters(cc_string* str, void* ctx) { #define REG_GET_LR() &r.gp_regs[35] #define REG_GET_CTR() &r.gp_regs[34] Dump_PPC() -#elif defined __riscv - #define REG_GNUM(num) &r.__gregs[num] - #define REG_GET_PC() &r.__gregs[REG_PC] - Dump_RISCV() #elif defined __mips__ #define REG_GNUM(num) &r.gregs[num] #define REG_GET_PC() &r.pc #define REG_GET_LO() &r.mdlo #define REG_GET_HI() &r.mdhi Dump_MIPS() +#elif defined __riscv + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_PC() &r.__gregs[REG_PC] + Dump_RISCV() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_SOLARIS /* See /usr/include/sys/regset.h */ +/* -> usr/src/uts/[ARCH]/sys/mcontext.h */ +/* -> usr/src/uts/[ARCH]/sys/regset.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; @@ -615,12 +617,16 @@ static void PrintRegisters(cc_string* str, void* ctx) { #elif defined __x86_64__ #define REG_GET(ign, reg) &r.gregs[REG_R##reg] Dump_X64() +#elif defined __sparc__ + #define REG_GET(ign, reg) &r.gregs[REG_##reg] + Dump_SPARC() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_NETBSD -/* See /usr/include/i386/mcontext.h */ +/* See /usr/include/[ARCH]/mcontext.h */ +/* -> src/sys/arch/[ARCH]/include/mcontext.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ @@ -629,12 +635,44 @@ static void PrintRegisters(cc_string* str, void* ctx) { #elif defined __x86_64__ #define REG_GET(ign, reg) &r.__gregs[_REG_R##reg] Dump_X64() +#elif defined __aarch64__ + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_FP() &r.__gregs[_REG_FP] + #define REG_GET_LR() &r.__gregs[_REG_LR] + #define REG_GET_SP() &r.__gregs[_REG_SP] + #define REG_GET_PC() &r.__gregs[_REG_PC] + Dump_ARM64() +#elif defined __arm__ + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_FP() &r.__gregs[_REG_FP] + #define REG_GET_IP() &r.__gregs[12] + #define REG_GET_SP() &r.__gregs[_REG_SP] + #define REG_GET_LR() &r.__gregs[_REG_LR] + #define REG_GET_PC() &r.__gregs[_REG_PC] + Dump_ARM32() +#elif defined __powerpc__ + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_PC() &r.__gregs[_REG_PC] + #define REG_GET_LR() &r.__gregs[_REG_LR] + #define REG_GET_CTR() &r.__gregs[_REG_CTR] + Dump_PPC() +#elif defined __mips__ + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_PC() &r.__gregs[_REG_EPC] + #define REG_GET_LO() &r.__gregs[_REG_MDLO] + #define REG_GET_HI() &r.__gregs[_REG_MDHI] + Dump_MIPS() +#elif defined __riscv + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_PC() &r.__gregs[_REG_PC] + Dump_RISCV() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_FREEBSD /* See /usr/include/machine/ucontext.h */ +/* -> src/sys/[ARCH]/include/ucontext.h */ static void PrintRegisters(cc_string* str, void* ctx) { mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; #if defined __i386__ @@ -643,20 +681,75 @@ static void PrintRegisters(cc_string* str, void* ctx) { #elif defined __x86_64__ #define REG_GET(reg, ign) &r.mc_r##reg Dump_X64() +#elif defined __aarch64__ + #define REG_GNUM(num) &r.mc_gpregs.gp_x[num] + #define REG_GET_FP() &r.mc_gpregs.gp_x[29] + #define REG_GET_LR() &r.mc_gpregs.gp_lr + #define REG_GET_SP() &r.mc_gpregs.gp_sp + #define REG_GET_PC() &r.mc_gpregs.gp_elr + Dump_ARM64() +#elif defined __arm__ + #define REG_GNUM(num) &r.__gregs[num] + #define REG_GET_FP() &r.__gregs[_REG_FP] + #define REG_GET_IP() &r.__gregs[12] + #define REG_GET_SP() &r.__gregs[_REG_SP] + #define REG_GET_LR() &r.__gregs[_REG_LR] + #define REG_GET_PC() &r.__gregs[_REG_PC] + Dump_ARM32() +#elif defined __powerpc__ + #define REG_GNUM(num) &r.mc_frame[##num] + #define REG_GET_PC() &r.mc_srr0 + #define REG_GET_LR() &r.mc_lr + #define REG_GET_CTR() &r.mc_ctr + Dump_PPC() +#elif defined __mips__ + #define REG_GNUM(num) &r.mc_regs[num] + #define REG_GET_PC() &r.mc_pc + #define REG_GET_LO() &r.mullo + #define REG_GET_HI() &r.mulhi + Dump_MIPS() #else #error "Unknown CPU architecture" #endif } #elif defined CC_BUILD_OPENBSD /* See /usr/include/machine/signal.h */ +/* -> src/sys/arch/[ARCH]/include/signal.h */ static void PrintRegisters(cc_string* str, void* ctx) { - struct sigcontext r = *((ucontext_t*)ctx); + ucontext_t* r = (ucontext_t*)ctx; #if defined __i386__ - #define REG_GET(reg, ign) &r.sc_e##reg + #define REG_GET(reg, ign) &r->sc_e##reg Dump_X86() #elif defined __x86_64__ - #define REG_GET(reg, ign) &r.sc_r##reg + #define REG_GET(reg, ign) &r->sc_r##reg Dump_X64() +#elif defined __aarch64__ + #define REG_GNUM(num) &r->sc_x[num] + #define REG_GET_FP() &r->sc_x[29] + #define REG_GET_LR() &r->sc_lr + #define REG_GET_SP() &r->sc_sp + #define REG_GET_PC() &r->sc_elr + Dump_ARM64() +#elif defined __arm__ + #define REG_GNUM(num) &r->sc_r##num + #define REG_GET_FP() &r->sc_r11 + #define REG_GET_IP() &r->sc_r12 + #define REG_GET_SP() &r->sc_usr_sp + #define REG_GET_LR() &r->sc_usr_lr + #define REG_GET_PC() &r->sc_pc + Dump_ARM32() +#elif defined __powerpc__ + #define REG_GNUM(num) &r->sc_frame.fixreg[num] + #define REG_GET_PC() &r->sc_frame.srr0 + #define REG_GET_LR() &r->sc_frame.lr + #define REG_GET_CTR() &r->sc_frame.ctr + Dump_PPC() +#elif defined __mips__ + #define REG_GNUM(num) &r->sc_regs[num] + #define REG_GET_PC() &r->sc_pc + #define REG_GET_LO() &r->mullo + #define REG_GET_HI() &r->mulhi + Dump_MIPS() #else #error "Unknown CPU architecture" #endif @@ -675,6 +768,7 @@ static void PrintRegisters(cc_string* str, void* ctx) { #endif } #endif + static void DumpRegisters(void* ctx) { cc_string str; char strBuffer[768]; String_InitArray(str, strBuffer); diff --git a/src/Window_X11.c b/src/Window_X11.c index c6d57b0c2..3f69ba063 100644 --- a/src/Window_X11.c +++ b/src/Window_X11.c @@ -251,7 +251,7 @@ void Window_Init(void) { Display* display = XOpenDisplay(NULL); int screen; - if (!display) Logger_Abort("Failed to open display"); + if (!display) Logger_Abort("Failed to open the X11 display. No X server running?"); screen = DefaultScreen(display); HookXErrors(); From 2bd5ed28d6a2779ba7a95d80b5af5ddaa4ffb28d Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Wed, 9 Nov 2022 23:05:23 +1100 Subject: [PATCH 02/16] WIP on save file dialog support, that allows saving a map to anywhere on disc as either a .cw or a .schematic --- src/Menus.c | 36 ++++++++++++++------- src/Window.h | 11 +++++-- src/Window_Carbon.c | 4 +++ src/Window_SDL.c | 4 +++ src/Window_Win.c | 76 ++++++++++++++++++++++++++++++--------------- 5 files changed, 92 insertions(+), 39 deletions(-) diff --git a/src/Menus.c b/src/Menus.c index 5006344b8..6c56848c8 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -1279,15 +1279,15 @@ static struct SaveLevelScreen { struct FontDesc titleFont, textFont; struct ButtonWidget save, alt, cancel; struct TextInputWidget input; - struct TextWidget mcEdit, desc; + struct TextWidget desc; } SaveLevelScreen; -static struct Widget* save_widgets[6] = { +static struct Widget* save_widgets[] = { (struct Widget*)&SaveLevelScreen.save, (struct Widget*)&SaveLevelScreen.alt, - (struct Widget*)&SaveLevelScreen.mcEdit, (struct Widget*)&SaveLevelScreen.cancel, + (struct Widget*)&SaveLevelScreen.cancel, (struct Widget*)&SaveLevelScreen.input, (struct Widget*)&SaveLevelScreen.desc, }; -#define SAVE_MAX_VERTICES (3 * BUTTONWIDGET_MAX + MENUINPUTWIDGET_MAX + 2 * TEXTWIDGET_MAX) +#define SAVE_MAX_VERTICES (3 * BUTTONWIDGET_MAX + MENUINPUTWIDGET_MAX + TEXTWIDGET_MAX) static void SaveLevelScreen_UpdateSave(struct SaveLevelScreen* s) { ButtonWidget_SetConst(&s->save, @@ -1352,7 +1352,7 @@ static void SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_string* res = Cw_Save(&compStream); #else if (String_CaselessEnds(path, &cw)) { - res = Cw_Save(&compStream); + res = Cw_Save(&compStream); /* TODO change to checking for schematic instead */ } else { res = Schematic_Save(&compStream); } @@ -1413,7 +1413,25 @@ static void SaveLevelScreen_Main(void* a, void* b) { SaveLevelScreen_Save(a, b, /* Use absolute path so data is written to memory filesystem instead of default filesystem */ static void SaveLevelScreen_Alt(void* a, void* b) { SaveLevelScreen_Save(a, b, "/%s.tmpmap"); } #else -static void SaveLevelScreen_Alt(void* a, void* b) { SaveLevelScreen_Save(a, b, "maps/%s.schematic"); } +static void SaveLevelScreen_UploadCallback(const cc_string* path) { + SaveLevelScreen_SaveMap(NULL, path); +} + +static void SaveLevelScreen_Alt(void* a, void* b) { + //SaveLevelScreen_Save(a, b, "maps/%s.schematic"); + static const char* const titles[] = { + "ClassiCube map", "MineCraft schematic", NULL + }; + static const char* const filters[] = { + ".cw", ".schematic", NULL + }; + static struct SaveFileDialogArgs args = { + filters, titles, SaveLevelScreen_UploadCallback + }; + + cc_result res = Window_SaveFileDialog(&args); + if (res) Logger_SimpleWarn(res, "showing save file dialog"); +} #endif static void SaveLevelScreen_Render(void* screen, double delta) { @@ -1468,9 +1486,6 @@ static void SaveLevelScreen_ContextRecreated(void* screen) { SaveLevelScreen_UpdateSave(s); SaveLevelScreen_UpdateAlt(s); -#ifndef CC_BUILD_WEB - TextWidget_SetConst(&s->mcEdit, "&eCan be imported into MCEdit", &s->textFont); -#endif TextInputWidget_SetFont(&s->input, &s->textFont); ButtonWidget_SetConst(&s->cancel, "Cancel", &s->titleFont); } @@ -1487,7 +1502,6 @@ static void SaveLevelScreen_Layout(void* screen) { Widget_SetLocation(&s->alt, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 70); #else Widget_SetLocation(&s->alt, ANCHOR_CENTRE, ANCHOR_CENTRE, -150, 120); - Widget_SetLocation(&s->mcEdit, ANCHOR_CENTRE, ANCHOR_CENTRE, 110, 120); #endif Menu_LayoutBack(&s->cancel); @@ -1511,10 +1525,8 @@ static void SaveLevelScreen_Init(void* screen) { ButtonWidget_Init(&s->save, 300, SaveLevelScreen_Main); #ifdef CC_BUILD_WEB ButtonWidget_Init(&s->alt, 300, SaveLevelScreen_Alt); - s->widgets[2] = NULL; /* null mcEdit widget */ #else ButtonWidget_Init(&s->alt, 200, SaveLevelScreen_Alt); - TextWidget_Init(&s->mcEdit); #endif ButtonWidget_Init(&s->cancel, 400, Menu_SwitchPause); diff --git a/src/Window.h b/src/Window.h index dc65baada..05134fdb2 100644 --- a/src/Window.h +++ b/src/Window.h @@ -131,17 +131,24 @@ CC_API void Window_ShowDialog(const char* title, const char* msg); #define OFD_UPLOAD_DELETE 0 /* (webclient) Deletes the uploaded file after invoking callback function */ #define OFD_UPLOAD_PERSIST 1 /* (webclient) Saves the uploded file into IndexedDB */ -typedef void (*OpenFileDialogCallback)(const cc_string* path); +typedef void (*FileDialogCallback)(const cc_string* path); +struct SaveFileDialogArgs { + const char* const* filters; /* File extensions to limit dialog to showing (e.g. ".zip", NULL) */ + const char* const* titles; /* Descriptions to show for each file extension */ + FileDialogCallback Callback; +}; struct OpenFileDialogArgs { const char* description; /* Describes the types of files supported (e.g. "Texture packs") */ const char* const* filters; /* File extensions to limit dialog to showing (e.g. ".zip", NULL) */ - OpenFileDialogCallback Callback; + FileDialogCallback Callback; int uploadAction; /* Action webclient takes after invoking callback function */ const char* uploadFolder; /* For webclient, folder to upload the file to */ }; /* Shows an 'load file' dialog window */ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args); +/* Shows an 'save file' dialog window */ +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args); /* Allocates a framebuffer that can be drawn/transferred to the window. */ /* NOTE: Do NOT free bmp->Scan0, use Window_FreeFramebuffer. */ diff --git a/src/Window_Carbon.c b/src/Window_Carbon.c index 3fedf74b1..a5f82dd16 100644 --- a/src/Window_Carbon.c +++ b/src/Window_Carbon.c @@ -595,6 +595,10 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { return ERR_NOT_SUPPORTED; } +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + return ERR_NOT_SUPPORTED; +} + static CGrafPtr fb_port; static struct Bitmap fb_bmp; static CGColorSpaceRef colorSpace; diff --git a/src/Window_SDL.c b/src/Window_SDL.c index 377fa59d3..7f3ae54b1 100644 --- a/src/Window_SDL.c +++ b/src/Window_SDL.c @@ -281,6 +281,10 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { return ERR_NOT_SUPPORTED; } +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + return ERR_NOT_SUPPORTED; +} + static SDL_Surface* win_surface; static SDL_Surface* blit_surface; diff --git a/src/Window_Win.c b/src/Window_Win.c index 2e278db8a..018b77f0c 100644 --- a/src/Window_Win.c +++ b/src/Window_Win.c @@ -551,52 +551,78 @@ static void ShowDialogCore(const char* title, const char* msg) { MessageBoxA(win_handle, msg, title, 0); } -cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { - const char* const* filters = args->filters; +static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback callback, cc_bool load) { cc_string path; char pathBuffer[NATIVE_STR_LEN]; WCHAR str[MAX_PATH] = { 0 }; OPENFILENAMEW ofn = { 0 }; WCHAR filter[MAX_PATH]; + BOOL ok; int i; - /* Filter tokens are \0 separated - e.g. "Maps (*.cw;*.dat)\0*.cw;*.dat\0 */ - String_InitArray(path, pathBuffer); - String_Format1(&path, "%c (", args->description); - for (i = 0; filters[i]; i++) - { - if (i) String_Append(&path, ';'); - String_Format1(&path, "*%c", filters[i]); - } - String_Append(&path, ')'); - String_Append(&path, '\0'); - - for (i = 0; filters[i]; i++) - { - if (i) String_Append(&path, ';'); - String_Format1(&path, "*%c", filters[i]); - } - String_Append(&path, '\0'); - Platform_EncodeUtf16(filter, &path); - + Platform_EncodeUtf16(filter, filters); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = win_handle; ofn.lpstrFile = str; ofn.nMaxFile = MAX_PATH; ofn.lpstrFilter = filter; ofn.nFilterIndex = 1; - ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + ofn.Flags = OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | (load ? OFN_FILEMUSTEXIST : OFN_OVERWRITEPROMPT); - if (!GetOpenFileNameW(&ofn)) - return CommDlgExtendedError(); + ok = load ? GetOpenFileNameW(&ofn) : GetSaveFileNameW(&ofn); + if (!ok) return CommDlgExtendedError(); String_InitArray(path, pathBuffer); for (i = 0; i < MAX_PATH && str[i]; i++) { String_Append(&path, Convert_CodepointToCP437(str[i])); } - args->Callback(&path); + callback(&path); return 0; } +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + const char* const* filters = args->filters; + cc_string str; char strBuffer[NATIVE_STR_LEN]; + int i; + + /* Filter tokens are \0 separated - e.g. "Maps (*.cw;*.dat)\0*.cw;*.dat\0 */ + String_InitArray(str, strBuffer); + String_Format1(&str, "%c (", args->description); + for (i = 0; filters[i]; i++) + { + if (i) String_Append(&str, ';'); + String_Format1(&str, "*%c", filters[i]); + } + String_Append(&str, ')'); + String_Append(&str, '\0'); + + for (i = 0; filters[i]; i++) + { + if (i) String_Append(&str, ';'); + String_Format1(&str, "*%c", filters[i]); + } + String_Append(&str, '\0'); + + return OpenSaveFileDialog(&str, args->Callback, true); +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + const char* const* titles = args->titles; + const char* const* filters = args->filters; + cc_string str; char strBuffer[NATIVE_STR_LEN]; + int i; + + /* Filter tokens are \0 separated - e.g. "Map (*.cw)\0*.cw\0 */ + String_InitArray(str, strBuffer); + for (i = 0; filters[i]; i++) + { + String_Format2(&str, "%c (*%c)", titles[i], filters[i]); + String_Append(&str, '\0'); + String_Format1(&str, "*%c", filters[i]); + String_Append(&str, '\0'); + } + return OpenSaveFileDialog(&str, args->Callback, false); +} + static HDC draw_DC; static HBITMAP draw_DIB; void Window_AllocFramebuffer(struct Bitmap* bmp) { From 1a2f6f838d9572bd98165d0aa0a5187d47ff32b8 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Thu, 10 Nov 2022 07:53:06 +1100 Subject: [PATCH 03/16] Align 'save file' to bottom instead, fix webclient compiling --- src/Menus.c | 142 +++++++++++++++++++---------------------------- src/Program.c | 4 +- src/Window_Web.c | 2 +- src/Window_Win.c | 56 ++++++++++--------- 4 files changed, 91 insertions(+), 113 deletions(-) diff --git a/src/Menus.c b/src/Menus.c index 6c56848c8..ab1070d6d 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -1277,13 +1277,13 @@ void ClassicGenScreen_Show(void) { static struct SaveLevelScreen { Screen_Body struct FontDesc titleFont, textFont; - struct ButtonWidget save, alt, cancel; + struct ButtonWidget save, file, cancel; struct TextInputWidget input; struct TextWidget desc; } SaveLevelScreen; static struct Widget* save_widgets[] = { - (struct Widget*)&SaveLevelScreen.save, (struct Widget*)&SaveLevelScreen.alt, + (struct Widget*)&SaveLevelScreen.save, (struct Widget*)&SaveLevelScreen.file, (struct Widget*)&SaveLevelScreen.cancel, (struct Widget*)&SaveLevelScreen.input, (struct Widget*)&SaveLevelScreen.desc, }; @@ -1294,24 +1294,11 @@ static void SaveLevelScreen_UpdateSave(struct SaveLevelScreen* s) { s->save.optName ? "&cOverwrite existing?" : "Save", &s->titleFont); } -static void SaveLevelScreen_UpdateAlt(struct SaveLevelScreen* s) { -#ifdef CC_BUILD_WEB - ButtonWidget_SetConst(&s->alt, "Download", &s->titleFont); -#else - ButtonWidget_SetConst(&s->alt, - s->alt.optName ? "&cOverwrite existing?" : "Save schematic", &s->titleFont); -#endif -} - static void SaveLevelScreen_RemoveOverwrites(struct SaveLevelScreen* s) { if (s->save.optName) { s->save.optName = NULL; SaveLevelScreen_UpdateSave(s); } - if (s->alt.optName) { - s->alt.optName = NULL; - SaveLevelScreen_UpdateAlt(s); - } } #ifdef CC_BUILD_WEB @@ -1321,13 +1308,10 @@ static void DownloadMap(const cc_string* path) { char strFile[NATIVE_STR_LEN]; cc_string file; cc_result res; - Platform_EncodeUtf8(strPath, path); - /* maps/aaa.schematic -> aaa.cw */ + Platform_EncodeUtf8(strPath, path); file = *path; Utils_UNSAFE_GetFilename(&file); - file.length = String_LastIndexOf(&file, '.'); - String_AppendConst(&file, ".cw"); - Platform_EncodeUtf8(strFile, &file); + latform_EncodeUtf8(strFile, file); res = interop_DownloadMap(strPath, strFile); if (res) { @@ -1338,58 +1322,50 @@ static void DownloadMap(const cc_string* path) { } #endif -static void SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_string* path) { - static const cc_string cw = String_FromConst(".cw"); +static cc_result SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_string* path) { + static const cc_string schematic = String_FromConst(".schematic"); struct Stream stream, compStream; struct GZipState state; cc_result res; res = Stream_CreateFile(&stream, path); - if (res) { Logger_SysWarn2(res, "creating", path); return; } + if (res) { Logger_SysWarn2(res, "creating", path); return res; } GZip_MakeStream(&compStream, &state, &stream); #ifdef CC_BUILD_WEB res = Cw_Save(&compStream); #else - if (String_CaselessEnds(path, &cw)) { - res = Cw_Save(&compStream); /* TODO change to checking for schematic instead */ - } else { + if (String_CaselessEnds(path, &schematic)) { res = Schematic_Save(&compStream); + } else { + res = Cw_Save(&compStream); } #endif if (res) { stream.Close(&stream); - Logger_SysWarn2(res, "encoding", path); return; + Logger_SysWarn2(res, "encoding", path); return res; } if ((res = compStream.Close(&compStream))) { stream.Close(&stream); - Logger_SysWarn2(res, "closing", path); return; + Logger_SysWarn2(res, "closing", path); return res; } res = stream.Close(&stream); - if (res) { Logger_SysWarn2(res, "closing", path); return; } + if (res) { Logger_SysWarn2(res, "closing", path); return res; } -#ifdef CC_BUILD_WEB - if (String_CaselessEnds(path, &cw)) { - Chat_Add1("&eSaved map to: %s", path); - } else { - DownloadMap(path); - } -#else - Chat_Add1("&eSaved map to: %s", path); -#endif World.LastSave = Game.Time; Gui_ShowPauseMenu(); + return 0; } -static void SaveLevelScreen_Save(void* screen, void* widget, const char* fmt) { +static void SaveLevelScreen_DoSave(void* screen, void* widget, const char* fmt) { cc_string path; char pathBuffer[FILENAME_SIZE]; - struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; struct ButtonWidget* btn = (struct ButtonWidget*)widget; cc_string file = s->input.base.text; + cc_result res; if (!file.length) { TextWidget_SetConst(&s->desc, "&ePlease enter a filename", &s->textFont); @@ -1402,23 +1378,38 @@ static void SaveLevelScreen_Save(void* screen, void* widget, const char* fmt) { if (File_Exists(&path) && !btn->optName) { btn->optName = ""; SaveLevelScreen_UpdateSave(s); - SaveLevelScreen_UpdateAlt(s); - } else { - SaveLevelScreen_RemoveOverwrites(s); - SaveLevelScreen_SaveMap(s, &path); + return; } -} -static void SaveLevelScreen_Main(void* a, void* b) { SaveLevelScreen_Save(a, b, "maps/%s.cw"); } + + SaveLevelScreen_RemoveOverwrites(s); + if ((res = SaveLevelScreen_SaveMap(s, &path))) return; + #ifdef CC_BUILD_WEB -/* Use absolute path so data is written to memory filesystem instead of default filesystem */ -static void SaveLevelScreen_Alt(void* a, void* b) { SaveLevelScreen_Save(a, b, "/%s.tmpmap"); } + if (btn == &s->save) { + Chat_Add1("&eSaved map to: %s", &path); + } else { + DownloadMap(&path); + } #else -static void SaveLevelScreen_UploadCallback(const cc_string* path) { - SaveLevelScreen_SaveMap(NULL, path); + Chat_Add1("&eSaved map to: %s", &path); +#endif } -static void SaveLevelScreen_Alt(void* a, void* b) { - //SaveLevelScreen_Save(a, b, "maps/%s.schematic"); +static void SaveLevelScreen_Save(void* a, void* b) { + SaveLevelScreen_DoSave(a, b, "maps/%s.cw"); +} + +#ifdef CC_BUILD_WEB +static void SaveLevelScreen_File(void* a, void* b) { + SaveLevelScreen_DoSave(a, b, "/tmpmaps/%s.cw"); +} +#else +static void SaveLevelScreen_UploadCallback(const cc_string* path) { + cc_result res = SaveLevelScreen_SaveMap(NULL, path); + if (!res) Chat_Add1("&eSaved map to: %s", path); +} + +static void SaveLevelScreen_File(void* a, void* b) { static const char* const titles[] = { "ClassiCube map", "MineCraft schematic", NULL }; @@ -1434,17 +1425,6 @@ static void SaveLevelScreen_Alt(void* a, void* b) { } #endif -static void SaveLevelScreen_Render(void* screen, double delta) { - PackedCol grey = PackedCol_Make(150, 150, 150, 255); - int x, y; - MenuScreen_Render2(screen, delta); - -#ifndef CC_BUILD_WEB - x = WindowInfo.Width / 2; y = WindowInfo.Height / 2; - Gfx_Draw2DFlat(x - 250, y + 90, 500, 2, grey); -#endif -} - static int SaveLevelScreen_KeyPress(void* screen, char keyChar) { struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; SaveLevelScreen_RemoveOverwrites(s); @@ -1484,10 +1464,14 @@ static void SaveLevelScreen_ContextRecreated(void* screen) { Screen_UpdateVb(screen); SaveLevelScreen_UpdateSave(s); - SaveLevelScreen_UpdateAlt(s); - TextInputWidget_SetFont(&s->input, &s->textFont); - ButtonWidget_SetConst(&s->cancel, "Cancel", &s->titleFont); + TextInputWidget_SetFont(&s->input, &s->textFont); + ButtonWidget_SetConst(&s->cancel, "Cancel", &s->titleFont); +#ifdef CC_BUILD_WEB + ButtonWidget_SetConst(&s->file, "Download", &s->titleFont); +#else + ButtonWidget_SetConst(&s->file, "Save file...", &s->titleFont); +#endif } static void SaveLevelScreen_Update(void* screen, double delta) { @@ -1497,20 +1481,12 @@ static void SaveLevelScreen_Update(void* screen, double delta) { static void SaveLevelScreen_Layout(void* screen) { struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; - Widget_SetLocation(&s->save, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 20); -#ifdef CC_BUILD_WEB - Widget_SetLocation(&s->alt, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 70); -#else - Widget_SetLocation(&s->alt, ANCHOR_CENTRE, ANCHOR_CENTRE, -150, 120); -#endif + Widget_SetLocation(&s->input, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -30); + Widget_SetLocation(&s->save, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 20); + Widget_SetLocation(&s->desc, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 65); + Widget_SetLocation(&s->file, ANCHOR_CENTRE, ANCHOR_MAX, 0, 70); Menu_LayoutBack(&s->cancel); - Widget_SetLocation(&s->input, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, -30); -#ifdef CC_BUILD_WEB - Widget_SetLocation(&s->desc, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 115); -#else - Widget_SetLocation(&s->desc, ANCHOR_CENTRE, ANCHOR_CENTRE, 0, 65); -#endif } static void SaveLevelScreen_Init(void* screen) { @@ -1522,12 +1498,8 @@ static void SaveLevelScreen_Init(void* screen) { s->maxVertices = SAVE_MAX_VERTICES; MenuInput_Path(desc); - ButtonWidget_Init(&s->save, 300, SaveLevelScreen_Main); -#ifdef CC_BUILD_WEB - ButtonWidget_Init(&s->alt, 300, SaveLevelScreen_Alt); -#else - ButtonWidget_Init(&s->alt, 200, SaveLevelScreen_Alt); -#endif + ButtonWidget_Init(&s->save, 400, SaveLevelScreen_Save); + ButtonWidget_Init(&s->file, 400, SaveLevelScreen_File); ButtonWidget_Init(&s->cancel, 400, Menu_SwitchPause); TextInputWidget_Create(&s->input, 500, &World.Name, &desc); @@ -1537,7 +1509,7 @@ static void SaveLevelScreen_Init(void* screen) { static const struct ScreenVTABLE SaveLevelScreen_VTABLE = { SaveLevelScreen_Init, SaveLevelScreen_Update, Menu_CloseKeyboard, - SaveLevelScreen_Render, Screen_BuildMesh, + MenuScreen_Render2, Screen_BuildMesh, SaveLevelScreen_KeyDown, Screen_InputUp, SaveLevelScreen_KeyPress, SaveLevelScreen_TextChanged, Menu_PointerDown, Screen_PointerUp, Menu_PointerMove, Screen_TMouseScroll, SaveLevelScreen_Layout, SaveLevelScreen_ContextLost, SaveLevelScreen_ContextRecreated diff --git a/src/Program.c b/src/Program.c index 3b2105220..b8afce155 100644 --- a/src/Program.c +++ b/src/Program.c @@ -75,8 +75,8 @@ static int RunProgram(int argc, char** argv) { #ifdef _MSC_VER /* NOTE: Make sure to comment this out before pushing a commit */ //cc_string rawArgs = String_FromConst("UnknownShadow200 fffff 127.0.0.1 25565"); - //cc_string rawArgs = String_FromConst("UnknownShadow200"); - //argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4); + cc_string rawArgs = String_FromConst("UnknownShadow200"); + argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4); #endif if (argsCount == 0) { diff --git a/src/Window_Web.c b/src/Window_Web.c index 7dad97ff8..386e82966 100644 --- a/src/Window_Web.c +++ b/src/Window_Web.c @@ -542,7 +542,7 @@ static void ShowDialogCore(const char* title, const char* msg) { interop_ShowDialog(title, msg); } -static OpenFileDialogCallback uploadCallback; +static FileDialogCallback uploadCallback; EMSCRIPTEN_KEEPALIVE void Window_OnFileUploaded(const char* src) { cc_string file; char buffer[FILENAME_SIZE]; String_InitArray(file, buffer); diff --git a/src/Window_Win.c b/src/Window_Win.c index 018b77f0c..60dccb151 100644 --- a/src/Window_Win.c +++ b/src/Window_Win.c @@ -551,7 +551,8 @@ static void ShowDialogCore(const char* title, const char* msg) { MessageBoxA(win_handle, msg, title, 0); } -static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback callback, cc_bool load) { +static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback callback, + const char* const* fileExts, cc_bool load) { cc_string path; char pathBuffer[NATIVE_STR_LEN]; WCHAR str[MAX_PATH] = { 0 }; OPENFILENAMEW ofn = { 0 }; @@ -575,52 +576,57 @@ static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback for (i = 0; i < MAX_PATH && str[i]; i++) { String_Append(&path, Convert_CodepointToCP437(str[i])); } + + /* Add default file extension if user didn't provide one */ + if (!load && ofn.nFileExtension == 0 && ofn.nFilterIndex > 0) { + String_AppendConst(&path, fileExts[ofn.nFilterIndex - 1]); + } callback(&path); return 0; } cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { - const char* const* filters = args->filters; - cc_string str; char strBuffer[NATIVE_STR_LEN]; + const char* const* fileExts = args->filters; + cc_string filters; char buffer[NATIVE_STR_LEN]; int i; /* Filter tokens are \0 separated - e.g. "Maps (*.cw;*.dat)\0*.cw;*.dat\0 */ - String_InitArray(str, strBuffer); - String_Format1(&str, "%c (", args->description); - for (i = 0; filters[i]; i++) + String_InitArray(filters, buffer); + String_Format1(&filters, "%c (", args->description); + for (i = 0; fileExts[i]; i++) { - if (i) String_Append(&str, ';'); - String_Format1(&str, "*%c", filters[i]); + if (i) String_Append(&filters, ';'); + String_Format1(&filters, "*%c", fileExts[i]); } - String_Append(&str, ')'); - String_Append(&str, '\0'); + String_Append(&filters, ')'); + String_Append(&filters, '\0'); - for (i = 0; filters[i]; i++) + for (i = 0; fileExts[i]; i++) { - if (i) String_Append(&str, ';'); - String_Format1(&str, "*%c", filters[i]); + if (i) String_Append(&filters, ';'); + String_Format1(&filters, "*%c", fileExts[i]); } - String_Append(&str, '\0'); + String_Append(&filters, '\0'); - return OpenSaveFileDialog(&str, args->Callback, true); + return OpenSaveFileDialog(&filters, args->Callback, fileExts, true); } cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { - const char* const* titles = args->titles; - const char* const* filters = args->filters; - cc_string str; char strBuffer[NATIVE_STR_LEN]; + const char* const* titles = args->titles; + const char* const* fileExts = args->filters; + cc_string filters; char buffer[NATIVE_STR_LEN]; int i; /* Filter tokens are \0 separated - e.g. "Map (*.cw)\0*.cw\0 */ - String_InitArray(str, strBuffer); - for (i = 0; filters[i]; i++) + String_InitArray(filters, buffer); + for (i = 0; fileExts[i]; i++) { - String_Format2(&str, "%c (*%c)", titles[i], filters[i]); - String_Append(&str, '\0'); - String_Format1(&str, "*%c", filters[i]); - String_Append(&str, '\0'); + String_Format2(&filters, "%c (*%c)", titles[i], fileExts[i]); + String_Append(&filters, '\0'); + String_Format1(&filters, "*%c", fileExts[i]); + String_Append(&filters, '\0'); } - return OpenSaveFileDialog(&str, args->Callback, false); + return OpenSaveFileDialog(&filters, args->Callback, fileExts, false); } static HDC draw_DC; From 70258163f1e93bf7e5a809b587a285675e4be381 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Thu, 10 Nov 2022 21:14:40 +1100 Subject: [PATCH 04/16] Add haiku, Linux, macOS cocoa save file dialog support Also fix haiku OS makefile --- src/Makefile | 4 +++ src/Window_Haiku.cpp | 44 ++++++++++++++++++----- src/Window_X11.c | 55 +++++++++++++++++++--------- src/interop_cocoa.m | 85 +++++++++++++++++++++++++++++--------------- 4 files changed, 134 insertions(+), 54 deletions(-) diff --git a/src/Makefile b/src/Makefile index 53a8fd035..bda3e70fa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -84,6 +84,7 @@ LIBS=-lexecinfo -lGL -lX11 -lXi -lm -lpthread endif ifeq ($(PLAT),haiku) +OBJECTS+=Window_Haiku.o CFLAGS=-g -pipe -fno-math-errno LDFLAGS=-g LIBS=-lm -lexecinfo -lGL -lnetwork -lstdc++ -lbe -lgame -ltracker @@ -135,3 +136,6 @@ $(C_OBJECTS): %.o : %.c interop_cocoa.o: interop_cocoa.m $(CC) $(CFLAGS) -c $< -o $@ + +Window_Haiku.o: Window_Haiku.cpp + $(CC) $(CFLAGS) -c $< -o $@ diff --git a/src/Window_Haiku.cpp b/src/Window_Haiku.cpp index 823c3f514..854bb8e9e 100644 --- a/src/Window_Haiku.cpp +++ b/src/Window_Haiku.cpp @@ -95,6 +95,7 @@ public: static void CallOpenFileCallback(const char* path); void CC_BApp::DispatchMessage(BMessage* msg, BHandler* handler) { CCEvent event = { 0 }; + const char* filename; entry_ref fileRef; switch (msg->what) @@ -110,6 +111,16 @@ void CC_BApp::DispatchMessage(BMessage* msg, BHandler* handler) { CallOpenFileCallback(path.Path()); } break; + case B_SAVE_REQUESTED: + // TODO do we need to support more than 1 ref? + if (msg->FindRef("directory", 0, &fileRef) == B_OK && + msg->FindString("name", &filename) == B_OK) { + BDirectory folder(&fileRef); + BPath path(&folder, filename); + // TODO add default file extension + CallOpenFileCallback(path.Path()); + } + break; default: //Platform_LogConst("UNHANDLED APP MESSAGE:"); //msg->PrintToStream(); @@ -503,8 +514,9 @@ static void ShowDialogCore(const char* title, const char* msg) { } static BFilePanel* open_panel; -static OpenFileDialogCallback open_callback; -static const char* const* open_filters; +static BFilePanel* save_panel; +static FileDialogCallback file_callback; +static const char* const* file_filters; class CC_BRefFilter : public BRefFilter { @@ -517,9 +529,9 @@ public: BPath path(ref); cc_string str = String_FromReadonly(path.Path()); - for (int i = 0; open_filters[i]; i++) + for (int i = 0; file_filters[i]; i++) { - cc_string ext = String_FromReadonly(open_filters[i]); + cc_string ext = String_FromReadonly(file_filters[i]); if (String_CaselessEnds(&str, &ext)) return true; } return false; @@ -529,11 +541,11 @@ public: static void CallOpenFileCallback(const char* rawPath) { cc_string path; char pathBuffer[1024]; String_InitArray(path, pathBuffer); - if (!open_callback) return; + if (!file_callback) return; String_AppendUtf8(&path, rawPath, String_Length(rawPath)); - open_callback(&path); - open_callback = NULL; + file_callback(&path); + file_callback = NULL; } cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { @@ -544,12 +556,26 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { // so this is technically a memory leak.. but meh } - open_callback = args->Callback; - open_filters = args->filters; + file_callback = args->Callback; + file_filters = args->filters; open_panel->Show(); return 0; } +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + if (!save_panel) { + save_panel = new BFilePanel(B_SAVE_PANEL); + save_panel->SetRefFilter(new CC_BRefFilter()); + // NOTE: the CC_BRefFilter is NOT owned by the BFilePanel, + // so this is technically a memory leak.. but meh + } + + file_callback = args->Callback; + file_filters = args->filters; + save_panel->Show(); + return 0; +} + static BBitmap* win_framebuffer; void Window_AllocFramebuffer(struct Bitmap* bmp) { // right/bottom coordinates are inclusive of the coordinates, diff --git a/src/Window_X11.c b/src/Window_X11.c index 3f69ba063..a054ffdc5 100644 --- a/src/Window_X11.c +++ b/src/Window_X11.c @@ -967,12 +967,31 @@ static void ShowDialogCore(const char* title, const char* msg) { XFlush(m.dpy); /* flush so window disappears immediately */ } -cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { - const char* const* filters = args->filters; +static cc_result OpenSaveFileDialog(const char* args, FileDialogCallback callback) { cc_string path; char pathBuffer[1024]; char result[4096] = { 0 }; int len, i; - FILE* fp; + /* TODO this doesn't detect when Zenity doesn't exist */ + FILE* fp = popen(args, "r"); + if (!fp) return 0; + + /* result from zenity is normally just one string */ + while (fgets(result, sizeof(result), fp)) { } + len = String_Length(result); + + if (len) { + String_InitArray(path, pathBuffer); + String_AppendUtf8(&path, result, len); + callback(&path); + } + pclose(fp); + return 0; +} + +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + const char* const* filters = args->filters; + cc_string path; char pathBuffer[1024]; + int i; String_InitArray_NT(path, pathBuffer); String_Format1(&path, "zenity --file-selection --file-filter='%c (", args->description); @@ -989,23 +1008,27 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { String_Format1(&path, " *%c", filters[i]); } String_AppendConst(&path, "'"); + path.buffer[path.length] = '\0'; + return OpenSaveFileDialog(path.buffer, args->Callback); +} - /* TODO this doesn't detect when Zenity doesn't exist */ - fp = popen(path.buffer, "r"); - if (!fp) return 0; +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + const char* const* titles = args->titles; + const char* const* fileExts = args->filters; + cc_string path; char pathBuffer[1024]; + int i; - /* result is normally just one string */ - while (fgets(result, sizeof(result), fp)) { } - len = String_Length(result); - - if (len) { - String_InitArray(path, pathBuffer); - String_AppendUtf8(&path, result, len); - args->Callback(&path); + String_InitArray_NT(path, pathBuffer); + String_AppendConst(&path, "zenity --file-selection"); + for (i = 0; fileExts[i]; i++) + { + String_Format3(&path, " --file-filter='%c (*%c) | *%c'", titles[i], fileExts[i], fileExts[i]); } - pclose(fp); - return 0; + String_AppendConst(&path, " --save --confirm-overwrite"); + + path.buffer[path.length] = '\0'; + return OpenSaveFileDialog(path.buffer, args->Callback); } static GC fb_gc; diff --git a/src/interop_cocoa.m b/src/interop_cocoa.m index 0a77082cd..fcf6c6ac1 100644 --- a/src/interop_cocoa.m +++ b/src/interop_cocoa.m @@ -530,43 +530,70 @@ void ShowDialogCore(const char* title, const char* msg) { CFRelease(msgCF); } -cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { - const char* const* filters = args->filters; - NSOpenPanel* dlg = [NSOpenPanel openPanel]; - NSArray* files; +static NSMutableArray* GetOpenSaveFilters(const char* const* filters) { + NSMutableArray* types = [NSMutableArray array]; + for (int i = 0; filters[i]; i++) + { + NSString* filter = [NSString stringWithUTF8String:filters[i]]; + filter = [filter substringFromIndex:1]; + [types addObject:filter]; + } + return types; +} + +static void OpenSaveDoCallback(NSURL* url, FileDialogCallback callback) { + NSString* str; + const char* src; + int len; + + str = [url path]; + src = [str UTF8String]; + len = String_Length(src); + + cc_string path; char pathBuffer[NATIVE_STR_LEN]; + String_InitArray(path, pathBuffer); + String_AppendUtf8(&path, src, len); + callback(&path); +} + +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + NSSavePanel* dlg = [NSSavePanel savePanel]; NSString* str; const char* src; int len, i; - NSMutableArray* types = [NSMutableArray array]; - for (i = 0; filters[i]; i++) - { - NSString* filter = [NSString stringWithUTF8String:filters[i]]; - filter = [filter substringFromIndex:1]; - [types addObject:filter]; - } - - [dlg setCanChooseFiles: YES]; - if ([dlg runModalForTypes:types] != NSOKButton) return 0; - // unfortunately below code doesn't work when linked against SDK < 10.6 - // https://developer.apple.com/documentation/appkit/nssavepanel/1534419-allowedfiletypes - // [dlg setAllowedFileTypes:types]; - // if ([dlg runModal] != NSOKButton) return 0; + NSMutableArray* types = GetOpenSaveFilters(args->filters); + [dlg setAllowedFileTypes:types]; + if ([dlg runModal] != NSOKButton) return 0; - files = [dlg URLs]; - if ([files count] < 1) return 0; - - str = [[files objectAtIndex:0] path]; - src = [str UTF8String]; - len = String_Length(src); - - cc_string path; char pathBuffer[NATIVE_STR_LEN]; - String_InitArray(path, pathBuffer); - String_AppendUtf8(&path, src, len); - args->Callback(&path); + NSURL* file = [dlg URL]; + if (file) OpenSaveDoCallback(file, args->Callback); return 0; } +cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { + const char* const* filters = args->filters; + NSOpenPanel* dlg = [NSOpenPanel openPanel]; + NSString* str; + const char* src; + int len, i; + + NSMutableArray* types = GetOpenSaveFilters(args->filters); + [dlg setCanChooseFiles: YES]; + if ([dlg runModalForTypes:types] != NSOKButton) return 0; + // unfortunately below code doesn't work when linked against SDK < 10.6 + // https://developer.apple.com/documentation/appkit/nssavepanel/1534419-allowedfiletypes + // [dlg setAllowedFileTypes:types]; + // if ([dlg runModal] != NSOKButton) return 0; + + NSArray* files = [dlg URLs]; + if ([files count] < 1) return 0; + + NSURL* file = [files objectAtIndex:0]; + OpenSaveDoCallback(file, args->Callback); + return 0; +} + static struct Bitmap fb_bmp; void Window_AllocFramebuffer(struct Bitmap* bmp) { bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels"); From 6aec5e83f25fa43adb55546bea2e8d8b7226d7e7 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Thu, 10 Nov 2022 23:08:12 +1100 Subject: [PATCH 05/16] Also allow saving maps as .mine that can be imported by minecraft classic, still a WIP though --- src/Formats.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/Formats.h | 9 +++-- src/Menus.c | 7 ++-- 3 files changed, 105 insertions(+), 5 deletions(-) diff --git a/src/Formats.c b/src/Formats.c index 02bf47062..2c6aa9423 100644 --- a/src/Formats.c +++ b/src/Formats.c @@ -1587,3 +1587,97 @@ cc_result Schematic_Save(struct Stream* stream) { } return Stream_Write(stream, sc_end, sizeof(sc_end)); } + +const int spawn_value = 8; +static const struct JField { + cc_uint8 type; + const char* name; + void* value; +} level_fields[] = { + { JFIELD_I32, "width", &World.Width }, + { JFIELD_I32, "depth", &World.Height }, + { JFIELD_I32, "height", &World.Length }, + { JFIELD_I32, "xSpawn", &spawn_value }, + { JFIELD_I32, "ySpawn", &spawn_value }, + { JFIELD_I32, "zSpawn", &spawn_value }, + /*{JFIELD_I32, "skyColor", &Env.SkyCol}, + { JFIELD_I32, "fogColor", &Env.FogCol}, + { JFIELD_I32, "cloudColor", &Env.CloudsCol},*/ + { JFIELD_ARRAY, "blocks", &World.Blocks } + /* TODO spawn, classic only blocks */ +}; + +static int WriteJavaString(cc_uint8* dst, const char* value) { + int length = String_Length(value); + dst[0] = 0; + dst[1] = length; + Mem_Copy(dst + 2, value, length); + return length; +} + +static cc_result WriteClassDesc(struct Stream* stream, cc_uint8 typecode, const char* klass, + int numFields, const struct JField* fields) { + cc_uint8 header[256] = { 0 }; + static const cc_uint8 footer[] = { + TC_ENDBLOCKDATA, /* classAnnotations */ + TC_NULL /* superClassDesc */ + }; + int i, length; + cc_result res; + + header[0] = typecode; + header[1] = TC_CLASSDESC; + length = WriteJavaString(header + 2, klass); + header[4 + length + 8] = SC_SERIALIZABLE; + header[4 + length + 9] = 0; + header[4 + length + 10] = numFields; + + if ((res = Stream_Write(stream, header, 15 + length))) return res; + + for (i = 0; i < numFields; i++) + { + header[0] = fields[i].type; + length = WriteJavaString(header + 1, fields[i].name); + + if (fields[i].type == JFIELD_ARRAY) { + header[3 + length + 0] = TC_STRING; + WriteJavaString(&header[3 + length + 1], "[B"); + length += 5; + } + if ((res = Stream_Write(stream, header, 3 + length))) return res; + } + + if ((res = Stream_Write(stream, footer, sizeof(footer)))) return res; + return 0; +} + +cc_result Dat_Save(struct Stream* stream) { + static const cc_uint8 header[] = { + /* DAT signature + version */ + 0x27,0x1B,0xB7,0x88, 0x02, + /* JSF signature + version */ + 0xAC,0xED, 0x00,0x05 + }; + cc_uint8 tmp[4]; + cc_result res; + int i; + + if ((res = Stream_Write(stream, header, sizeof(header)))) return res; + if ((res = WriteClassDesc(stream, TC_OBJECT, "com.mojang.minecraft.level.Level", + Array_Elems(level_fields), level_fields))) return res; + + /* Write field values */ + for (i = 0; i < Array_Elems(level_fields); i++) + { + if (level_fields[i].type == JFIELD_I32) { + Stream_SetU32_BE(tmp, *((int*)level_fields[i].value)); + if ((res = Stream_Write(stream, tmp, 4))) return res; + } else { + if ((res = WriteClassDesc(stream, TC_ARRAY, "[B", 0, NULL))) return res; + Stream_SetU32_BE(tmp, World.Volume); + if ((res = Stream_Write(stream, tmp, 4))) return res; + if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res; + } + } + return 0; +} \ No newline at end of file diff --git a/src/Formats.h b/src/Formats.h index db68932f6..1bfb493bb 100644 --- a/src/Formats.h +++ b/src/Formats.h @@ -29,9 +29,12 @@ cc_result Cw_Load(struct Stream* stream); cc_result Dat_Load(struct Stream* stream); /* Exports a world to a .cw ClassicWorld map file. */ -/* Compatible with ClassiCube/ClassicalSharp. */ +/* Compatible with ClassiCube/ClassicalSharp */ cc_result Cw_Save(struct Stream* stream); -/* Exports a world to a .schematic Schematic map file. */ -/* Used by MCEdit and other tools. */ +/* Exports a world to a .schematic Schematic map file */ +/* Used by MCEdit and other tools */ cc_result Schematic_Save(struct Stream* stream); +/* Exports a world to a .dat Classic map file */ +/* Used by MineCraft Classic */ +cc_result Dat_Save(struct Stream* stream); #endif diff --git a/src/Menus.c b/src/Menus.c index ab1070d6d..34ca192c6 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -1324,6 +1324,7 @@ static void DownloadMap(const cc_string* path) { static cc_result SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_string* path) { static const cc_string schematic = String_FromConst(".schematic"); + static const cc_string mine = String_FromConst(".mine"); struct Stream stream, compStream; struct GZipState state; cc_result res; @@ -1337,6 +1338,8 @@ static cc_result SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_str #else if (String_CaselessEnds(path, &schematic)) { res = Schematic_Save(&compStream); + } else if (String_CaselessEnds(path, &mine)) { + res = Dat_Save(&compStream); } else { res = Cw_Save(&compStream); } @@ -1411,10 +1414,10 @@ static void SaveLevelScreen_UploadCallback(const cc_string* path) { static void SaveLevelScreen_File(void* a, void* b) { static const char* const titles[] = { - "ClassiCube map", "MineCraft schematic", NULL + "ClassiCube map", "MineCraft schematic", "MineCraft classic map", NULL }; static const char* const filters[] = { - ".cw", ".schematic", NULL + ".cw", ".schematic", ".mine", NULL }; static struct SaveFileDialogArgs args = { filters, titles, SaveLevelScreen_UploadCallback From 8bc07b308a850f41179de946364d2ca2252eaf96 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Fri, 11 Nov 2022 21:40:58 +1100 Subject: [PATCH 06/16] Rewrite packet sending functions to avoid depending on global temp array Also change X11 backend to log visual ID of created window --- src/Protocol.c | 52 ++++++++++++++++++++++++++----------------- src/Protocol.h | 5 ++--- src/Server.c | 50 +++++++++++------------------------------ src/Server.h | 58 ++++++++++++++++++++++++------------------------ src/Window_X11.c | 2 +- 5 files changed, 77 insertions(+), 90 deletions(-) diff --git a/src/Protocol.c b/src/Protocol.c index 3b3417673..5b906afd9 100644 --- a/src/Protocol.c +++ b/src/Protocol.c @@ -380,11 +380,10 @@ void Classic_SendChat(const cc_string* text, cc_bool partial) { Server.SendData(data, 66); } -void Classic_WritePosition(Vec3 pos, float yaw, float pitch) { +static cc_uint8* Classic_WritePosition(cc_uint8* data, Vec3 pos, float yaw, float pitch) { BlockID payload; int x, y, z; - cc_uint8* data = Server.WriteBuffer; *data++ = OPCODE_ENTITY_TELEPORT; { payload = cpe_sendHeldBlock ? Inventory_SelectedBlock : ENTITIES_SELF_ID; @@ -406,11 +405,13 @@ void Classic_WritePosition(Vec3 pos, float yaw, float pitch) { *data++ = Math_Deg2Packed(yaw); *data++ = Math_Deg2Packed(pitch); } - Server.WriteBuffer = data; + return data; } -void Classic_WriteSetBlock(int x, int y, int z, cc_bool place, BlockID block) { - cc_uint8* data = Server.WriteBuffer; +void Classic_SendSetBlock(int x, int y, int z, cc_bool place, BlockID block) { + cc_uint8 tmp[32]; + cc_uint8* data = tmp; + *data++ = OPCODE_SET_BLOCK_CLIENT; { Stream_SetU16_BE(data, x); data += 2; @@ -419,7 +420,7 @@ void Classic_WriteSetBlock(int x, int y, int z, cc_bool place, BlockID block) { *data++ = place; WriteBlock(data, block); } - Server.WriteBuffer = data; + Server.SendData(tmp, (cc_uint32)(data - tmp)); } #define Classic_HandshakeSize() (Game_Version.Protocol > PROTOCOL_0019 ? 131 : 130) @@ -742,12 +743,13 @@ static void Classic_Reset(void) { Net_Set(OPCODE_SET_PERMISSION, Classic_SetPermission, 2); } -static void Classic_Tick(void) { +static cc_uint8* Classic_Tick(cc_uint8* data) { struct Entity* e = &LocalPlayer_Instance.Base; - if (!classic_receivedFirstPos) return; + if (!classic_receivedFirstPos) return data; + /* 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(e->next.pos, e->Yaw, e->Pitch); + return Classic_WritePosition(data, e->next.pos, e->Yaw, e->Pitch); } @@ -830,14 +832,13 @@ static void CPE_SendExtEntry(const cc_string* extName, int extVersion) { Server.SendData(data, 69); } -static void CPE_WriteTwoWayPing(cc_bool serverToClient, int id) { - cc_uint8* data = Server.WriteBuffer; +static cc_uint8* CPE_WriteTwoWayPing(cc_uint8* data, cc_bool serverToClient, int id) { *data++ = OPCODE_TWO_WAY_PING; { *data++ = serverToClient; Stream_SetU16_BE(data, id); data += 2; } - Server.WriteBuffer = data; + return data; } static void CPE_SendCpeExtInfoReply(void) { @@ -1320,11 +1321,14 @@ static void CPE_SetEntityProperty(cc_uint8* data) { static void CPE_TwoWayPing(cc_uint8* data) { cc_uint8 serverToClient = data[0]; int id = Stream_GetU16_BE(data + 1); + cc_uint8 tmp[32]; - if (serverToClient) { - CPE_WriteTwoWayPing(true, id); /* server to client reply */ - Net_SendPacket(); - } else { Ping_Update(id); } + /* handle client>server ping response from server */ + if (!serverToClient) { Ping_Update(id); return; } + + /* send server>client ping response to server */ + data = CPE_WriteTwoWayPing(tmp, true, id); + Server.SendData(tmp, (cc_uint32)(data - tmp)); } static void CPE_SetInventoryOrder(cc_uint8* data) { @@ -1617,12 +1621,13 @@ static void CPE_Reset(void) { Net_Set(OPCODE_ENTITY_TELEPORT_EXT, CPE_ExtEntityTeleport, 11); } -static void CPE_Tick(void) { +static cc_uint8* CPE_Tick(cc_uint8* data) { cpe_pingTicks++; if (cpe_pingTicks >= 20 && cpe_twoWayPing) { - CPE_WriteTwoWayPing(false, Ping_NextPingId()); + data = CPE_WriteTwoWayPing(data, false, Ping_NextPingId()); cpe_pingTicks = 0; } + return data; } @@ -1764,9 +1769,16 @@ static void Protocol_Reset(void) { } void Protocol_Tick(void) { - Classic_Tick(); - CPE_Tick(); + cc_uint8 tmp[256]; + cc_uint8* data = tmp; + + data = Classic_Tick(data); + data = CPE_Tick(data); WoM_Tick(); + + /* Have any packets been written? */ + if (data == tmp) return; + Server.SendData(tmp, (cc_uint32)(data - tmp)); } static void OnInit(void) { diff --git a/src/Protocol.h b/src/Protocol.h index 4d7bd2db9..b096e00e0 100644 --- a/src/Protocol.h +++ b/src/Protocol.h @@ -65,9 +65,8 @@ void Protocol_RemoveEntity(EntityID id); void Protocol_Tick(void); extern cc_bool cpe_needD3Fix; -void Classic_SendChat(const cc_string* text, cc_bool partial); -void Classic_WritePosition(Vec3 pos, float yaw, float pitch); -void Classic_WriteSetBlock(int x, int y, int z, cc_bool place, BlockID block); +void Classic_SendChat(const cc_string* text, cc_bool partial);\ +void Classic_SendSetBlock(int x, int y, int z, cc_bool place, BlockID block); void Classic_SendLogin(void); void CPE_SendPlayerClick(int button, cc_bool pressed, cc_uint8 targetId, struct RayTracer* t); diff --git a/src/Server.c b/src/Server.c index bec91ffa9..4100695a3 100644 --- a/src/Server.c +++ b/src/Server.c @@ -177,16 +177,15 @@ static void SPConnection_SendBlock(int x, int y, int z, BlockID old, BlockID now Physics_OnBlockChanged(x, y, z, old, now); } -static void SPConnection_SendPosition(Vec3 pos, float yaw, float pitch) { } static void SPConnection_SendData(const cc_uint8* data, cc_uint32 len) { } static void SPConnection_Tick(struct ScheduledTask* task) { if (Server.Disconnected) return; - if ((ticks % 3) == 0) { /* 60 -> 20 ticks a second */ - Physics_Tick(); - TexturePack_CheckPending(); - } - ticks++; + /* 60 -> 20 ticks a second */ + if ((ticks++ % 3) != 0) return; + + Physics_Tick(); + TexturePack_CheckPending(); } static void SPConnection_Init(void) { @@ -197,13 +196,11 @@ static void SPConnection_Init(void) { Server.Tick = SPConnection_Tick; Server.SendBlock = SPConnection_SendBlock; Server.SendChat = SPConnection_SendChat; - Server.SendPosition = SPConnection_SendPosition; Server.SendData = SPConnection_SendData; Server.SupportsFullCP437 = !Game_ClassicMode; Server.SupportsPartialMessages = true; Server.IsSinglePlayer = true; - Server.WriteBuffer = NULL; } @@ -212,7 +209,6 @@ static void SPConnection_Init(void) { *#########################################################################################################################*/ static cc_socket net_socket; static cc_uint8 net_readBuffer[4096 * 5]; -static cc_uint8 net_writeBuffer[131]; static cc_uint8* net_readCurrent; static cc_result net_writeFailure; @@ -229,9 +225,7 @@ static void MPConnection_FinishConnect(void) { Event_RaiseVoid(&NetEvents.Connected); Event_RaiseFloat(&WorldEvents.Loading, 0.0f); - net_readCurrent = net_readBuffer; - Server.WriteBuffer = net_writeBuffer; - + net_readCurrent = net_readBuffer; Classic_SendLogin(); lastPacket = Game.Time; } @@ -311,11 +305,10 @@ static void MPConnection_BeginConnect(void) { static void MPConnection_SendBlock(int x, int y, int z, BlockID old, BlockID now) { if (now == BLOCK_AIR) { now = Inventory_SelectedBlock; - Classic_WriteSetBlock(x, y, z, false, now); + Classic_SendSetBlock(x, y, z, false, now); } else { - Classic_WriteSetBlock(x, y, z, true, now); + Classic_SendSetBlock(x, y, z, true, now); } - Net_SendPacket(); } static void MPConnection_SendChat(const cc_string* text) { @@ -330,11 +323,6 @@ static void MPConnection_SendChat(const cc_string* text) { Classic_SendChat(&left, false); } -static void MPConnection_SendPosition(Vec3 pos, float yaw, float pitch) { - Classic_WritePosition(pos, yaw, pitch); - Net_SendPacket(); -} - static void MPConnection_Disconnect(void) { static const cc_string title = String_FromConst("Disconnected!"); static const cc_string reason = String_FromConst("You've lost connection to the server"); @@ -442,13 +430,10 @@ static void MPConnection_Tick(struct ScheduledTask* task) { } /* Network is ticked 60 times a second. We only send position updates 20 times a second */ - if ((ticks % 3) == 0) { - TexturePack_CheckPending(); - Protocol_Tick(); - /* Have any packets been written? */ - if (Server.WriteBuffer != net_writeBuffer) Net_SendPacket(); - } - ticks++; + if ((ticks++ % 3) != 0) return; + + TexturePack_CheckPending(); + Protocol_Tick(); } static void MPConnection_SendData(const cc_uint8* data, cc_uint32 len) { @@ -475,12 +460,6 @@ static void MPConnection_SendData(const cc_uint8* data, cc_uint32 len) { } } -void Net_SendPacket(void) { - cc_uint32 len = (cc_uint32)(Server.WriteBuffer - net_writeBuffer); - Server.WriteBuffer = net_writeBuffer; - Server.SendData(net_writeBuffer, len); -} - static void MPConnection_Init(void) { Server_ResetState(); Server.IsSinglePlayer = false; @@ -489,11 +468,8 @@ static void MPConnection_Init(void) { Server.Tick = MPConnection_Tick; Server.SendBlock = MPConnection_SendBlock; Server.SendChat = MPConnection_SendChat; - Server.SendPosition = MPConnection_SendPosition; Server.SendData = MPConnection_SendData; - - net_readCurrent = net_readBuffer; - Server.WriteBuffer = net_writeBuffer; + net_readCurrent = net_readBuffer; } diff --git a/src/Server.h b/src/Server.h index 1c8fc40f4..025199d72 100644 --- a/src/Server.h +++ b/src/Server.h @@ -1,6 +1,6 @@ #ifndef CC_SERVERCONNECTION_H #define CC_SERVERCONNECTION_H -#include "Vectors.h" +#include "Core.h" /* Represents a connection to either a multiplayer or an internal singleplayer server Copyright 2014-2022 ClassiCube | Licensed under BSD-3 @@ -10,62 +10,62 @@ struct IGameComponent; struct ScheduledTask; extern struct IGameComponent Server_Component; -/* Prepares a ping entry for sending to the server, then returns its ID. */ +/* Prepares a ping entry for sending to the server, then returns its ID */ int Ping_NextPingId(void); -/* Updates received time for ping entry with matching ID. */ +/* Updates received time for ping entry with matching ID */ void Ping_Update(int id); -/* Calculates average ping time based on most recent ping entries. */ +/* Calculates average ping time based on most recent ping entries */ int Ping_AveragePingMS(void); -/* Data for currently active connection to a server. */ +/* Data for currently active connection to a server */ CC_VAR extern struct _ServerConnectionData { - /* Begins connecting to the server. */ - /* NOTE: Usually asynchronous, but not always. */ + /* Begins connecting to the server */ + /* NOTE: Usually asynchronous, but not always */ void (*BeginConnect)(void); /* Ticks state of the server. */ void (*Tick)(struct ScheduledTask* task); - /* Sends a block update to the server. */ + /* Sends a block update to the server */ void (*SendBlock)(int x, int y, int z, BlockID old, BlockID now); - /* Sends a chat message to the server. */ + /* Sends a chat message to the server */ void (*SendChat)(const cc_string* text); - /* Sends a position update to the server. */ - void (*SendPosition)(Vec3 pos, float yaw, float pitch); + /* NOTE: Deprecated and removed - change LocalPlayer's position instead */ + /* Was a function pointer to send a position update to the multiplayer server */ + void (*__Unused)(void); /* Sends raw data to the server. */ - /* NOTE: Prefer SendBlock/Position/Chat instead, this does NOT work in singleplayer. */ + /* NOTE: Prefer SendBlock/SendChat instead, this does NOT work in singleplayer */ void (*SendData)(const cc_uint8* data, cc_uint32 len); - /* The current name of the server. (Shows as first line when loading) */ + /* The current name of the server (Shows as first line when loading) */ cc_string Name; - /* The current MOTD of the server. (Shows as second line when loading) */ + /* The current MOTD of the server (Shows as second line when loading) */ cc_string MOTD; - /* The software name the client identifies itself as being to the server. */ - /* By default this is GAME_APP_NAME. */ + /* The software name the client identifies itself as being to the server */ + /* By default this is GAME_APP_NAME */ cc_string AppName; - /* Buffer to data to send to the server. */ - cc_uint8* WriteBuffer; - /* Whether the player is connected to singleplayer/internal server. */ + /* NOTE: Drprecated, was a pointer to a temp buffer */ + cc_uint8* ___unused; + /* Whether the player is connected to singleplayer/internal server */ cc_bool IsSinglePlayer; - /* Whether the player has been disconnected from the server. */ + /* Whether the player has been disconnected from the server */ cc_bool Disconnected; - /* Whether the server supports separate tab list from entities in world. */ + /* Whether the server supports separate tab list from entities in world */ cc_bool SupportsExtPlayerList; - /* Whether the server supports packet with detailed info on mouse clicks. */ + /* Whether the server supports packet with detailed info on mouse clicks */ cc_bool SupportsPlayerClick; - /* Whether the server supports combining multiple chat packets into one. */ + /* Whether the server supports combining multiple chat packets into one */ cc_bool SupportsPartialMessages; - /* Whether the server supports all of code page 437, not just ASCII. */ + /* Whether the server supports all of code page 437, not just ASCII */ cc_bool SupportsFullCP437; - /* Address of the server if multiplayer, empty string if singleplayer. */ + /* Address of the server if multiplayer, empty string if singleplayer */ cc_string Address; - /* Port of the server if multiplayer, 0 if singleplayer. */ + /* Port of the server if multiplayer, 0 if singleplayer */ int Port; } Server; -/* If user hasn't previously accepted url, displays a dialog asking to confirm downloading it. */ -/* Otherwise just calls TexturePack_Extract. */ +/* If user hasn't previously accepted url, displays a dialog asking to confirm downloading it */ +/* Otherwise just calls TexturePack_Extract */ void Server_RetrieveTexturePack(const cc_string* url); -void Net_SendPacket(void); #endif diff --git a/src/Window_X11.c b/src/Window_X11.c index 3f69ba063..445da8a01 100644 --- a/src/Window_X11.c +++ b/src/Window_X11.c @@ -292,7 +292,7 @@ static void DoCreateWindow(int width, int height) { RegisterAtoms(); win_visual = GLContext_SelectVisual(); - Platform_LogConst("Opening render window... "); + Platform_Log1("Created window (visual id: %h)", &win_visual.visualid); attributes.colormap = XCreateColormap(win_display, win_rootWin, win_visual.visual, AllocNone); attributes.event_mask = win_eventMask; From 813fb533f97e77dd4385b4641df13d578236e8a1 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Fri, 11 Nov 2022 22:51:01 +1100 Subject: [PATCH 07/16] Use more verbose error messages for 404/403/401 texture pack download errors --- src/TexturePack.c | 12 +++++++++--- src/Window_SDL.c | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/TexturePack.c b/src/TexturePack.c index 3edb07e2f..4eb353c75 100644 --- a/src/TexturePack.c +++ b/src/TexturePack.c @@ -409,10 +409,16 @@ void TexturePack_CheckPending(void) { Mem_Free(item.data); } else if (item.result) { Logger_Warn(item.result, "trying to download texture pack", Http_DescribeError); + } else if (item.statusCode == 200 || item.statusCode == 304) { + /* Empty responses is okay for these status codes, so don't log an error */ + } else if (item.statusCode == 404) { + Chat_AddRaw("&c404 Not Found error when trying to download texture pack"); + Chat_AddRaw(" &cThe texture pack URL may be incorrect or no longer exist"); + } else if (item.statusCode == 401 || item.statusCode == 403) { + Chat_Add1("&c%i Not Authorised error when trying to download texture pack", &item.statusCode); + Chat_AddRaw(" &cThe texture pack URL may not be publicly shared"); } else { - int status = item.statusCode; - if (status == 200 || status == 304) return; - Chat_Add1("&c%i error when trying to download texture pack", &status); + Chat_Add1("&c%i error when trying to download texture pack", &item.statusCode); } } diff --git a/src/Window_SDL.c b/src/Window_SDL.c index 377fa59d3..5b08849bc 100644 --- a/src/Window_SDL.c +++ b/src/Window_SDL.c @@ -9,6 +9,8 @@ #include static SDL_Window* win_handle; +#error "Some features are missing from the SDL backend. If possible, it is recommended that you use a native windowing backend instead" + static void RefreshWindowBounds(void) { SDL_GetWindowSize(win_handle, &WindowInfo.Width, &WindowInfo.Height); } From 0e916bfeed4e5cbdf654699496f5535ca2d69312 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 12 Nov 2022 12:17:46 +1100 Subject: [PATCH 08/16] Now write a spawn position to .mine files, also redesign webclient map downloading to be more consistent with other platforms and not rely on special CC_BUILD_WEB only code --- src/Errors.h | 5 ++- src/Formats.c | 32 +++++++++---------- src/Menus.c | 77 +++++++++++++++------------------------------- src/Window.h | 1 + src/Window_Web.c | 18 +++++++++++ src/interop_web.js | 2 +- 6 files changed, 62 insertions(+), 73 deletions(-) diff --git a/src/Errors.h b/src/Errors.h index c9f595a50..b5e8e0045 100644 --- a/src/Errors.h +++ b/src/Errors.h @@ -19,12 +19,11 @@ enum CC_ERRORS { WAV_ERR_STREAM_HDR = 0xCCDED007UL, /* Bytes #1-#4 aren't "RIFF" */ WAV_ERR_STREAM_TYPE = 0xCCDED008UL, /* Bytes #9-#12 aren't "WAV " */ WAV_ERR_DATA_TYPE = 0xCCDED009UL, /* Audio data type isn't 1 (PCM) */ - /*WAV_ERR_NO_DATA = 0xCCDED00AUL, repurposed for AUDIO_ERR_MP3_SIG */ + AUDIO_ERR_MP3_SIG = 0xCCDED00AUL, /* Signature bytes are "ID3" */ WAV_ERR_SAMPLE_BITS = 0xCCDED00BUL, /* Bits per sample isn't 16 */ - AUDIO_ERR_MP3_SIG = 0xCCDED00AUL, /* Signature bytes are "ID3" (repurposed WAV_ERR_NO_DATA) */ + SFD_ERR_NEED_DEFAULT_NAME = 0xCCDED00CUL, - /*VORBIS_ERR_HEADER = 0xCCDED00CUL, no longer used */ VORBIS_ERR_WRONG_HEADER = 0xCCDED00DUL, /* Packet header doesn't match expected type */ VORBIS_ERR_FRAMING = 0xCCDED00EUL, /* Framing flag doesn't match expected value */ VORBIS_ERR_VERSION = 0xCCDED00FUL, /* Vorbis version isn't 0 */ diff --git a/src/Formats.c b/src/Formats.c index 2c6aa9423..3dc90003e 100644 --- a/src/Formats.c +++ b/src/Formats.c @@ -1588,23 +1588,19 @@ cc_result Schematic_Save(struct Stream* stream) { return Stream_Write(stream, sc_end, sizeof(sc_end)); } -const int spawn_value = 8; static const struct JField { - cc_uint8 type; + cc_uint8 type, isFloat; const char* name; void* value; } level_fields[] = { - { JFIELD_I32, "width", &World.Width }, - { JFIELD_I32, "depth", &World.Height }, - { JFIELD_I32, "height", &World.Length }, - { JFIELD_I32, "xSpawn", &spawn_value }, - { JFIELD_I32, "ySpawn", &spawn_value }, - { JFIELD_I32, "zSpawn", &spawn_value }, - /*{JFIELD_I32, "skyColor", &Env.SkyCol}, - { JFIELD_I32, "fogColor", &Env.FogCol}, - { JFIELD_I32, "cloudColor", &Env.CloudsCol},*/ - { JFIELD_ARRAY, "blocks", &World.Blocks } - /* TODO spawn, classic only blocks */ + { JFIELD_I32, false, "width", &World.Width }, + { JFIELD_I32, false, "depth", &World.Height }, + { JFIELD_I32, false, "height", &World.Length }, + { JFIELD_I32, true, "xSpawn", &LocalPlayer_Instance.Base.Position.X }, + { JFIELD_I32, true, "ySpawn", &LocalPlayer_Instance.Base.Position.Y }, + { JFIELD_I32, true, "zSpawn", &LocalPlayer_Instance.Base.Position.Z }, + { JFIELD_ARRAY,0, "blocks" } + /* TODO classic only blocks */ }; static int WriteJavaString(cc_uint8* dst, const char* value) { @@ -1658,9 +1654,10 @@ cc_result Dat_Save(struct Stream* stream) { /* JSF signature + version */ 0xAC,0xED, 0x00,0x05 }; + const struct JField* field; cc_uint8 tmp[4]; cc_result res; - int i; + int i, value; if ((res = Stream_Write(stream, header, sizeof(header)))) return res; if ((res = WriteClassDesc(stream, TC_OBJECT, "com.mojang.minecraft.level.Level", @@ -1669,8 +1666,11 @@ cc_result Dat_Save(struct Stream* stream) { /* Write field values */ for (i = 0; i < Array_Elems(level_fields); i++) { - if (level_fields[i].type == JFIELD_I32) { - Stream_SetU32_BE(tmp, *((int*)level_fields[i].value)); + field = &level_fields[i]; + + if (field->type == JFIELD_I32) { + value = field->isFloat ? *((float*)field->value) : *((int*)field->value); + Stream_SetU32_BE(tmp, value); if ((res = Stream_Write(stream, tmp, 4))) return res; } else { if ((res = WriteClassDesc(stream, TC_ARRAY, "[B", 0, NULL))) return res; diff --git a/src/Menus.c b/src/Menus.c index 34ca192c6..b9102bc40 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -31,6 +31,7 @@ #include "Options.h" #include "Input.h" #include "Utils.h" +#include "Errors.h" /* Describes a menu option button */ struct MenuOptionDesc { @@ -1301,28 +1302,7 @@ static void SaveLevelScreen_RemoveOverwrites(struct SaveLevelScreen* s) { } } -#ifdef CC_BUILD_WEB -extern int interop_DownloadMap(const char* path, const char* filename); -static void DownloadMap(const cc_string* path) { - char strPath[NATIVE_STR_LEN]; - char strFile[NATIVE_STR_LEN]; - cc_string file; - cc_result res; - - Platform_EncodeUtf8(strPath, path); - file = *path; Utils_UNSAFE_GetFilename(&file); - latform_EncodeUtf8(strFile, file); - - res = interop_DownloadMap(strPath, strFile); - if (res) { - Logger_SysWarn2(res, "Downloading map", &file); - } else { - Chat_Add1("&eDownloaded map: %s", &file); - } -} -#endif - -static cc_result SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_string* path) { +static cc_result SaveLevelScreen_SaveMap(const cc_string* path) { static const cc_string schematic = String_FromConst(".schematic"); static const cc_string mine = String_FromConst(".mine"); struct Stream stream, compStream; @@ -1363,10 +1343,10 @@ static cc_result SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_str return 0; } -static void SaveLevelScreen_DoSave(void* screen, void* widget, const char* fmt) { - cc_string path; char pathBuffer[FILENAME_SIZE]; +static void SaveLevelScreen_Save(void* screen, void* widget) { struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; struct ButtonWidget* btn = (struct ButtonWidget*)widget; + cc_string path; char pathBuffer[FILENAME_SIZE]; cc_string file = s->input.base.text; cc_result res; @@ -1374,9 +1354,10 @@ static void SaveLevelScreen_DoSave(void* screen, void* widget, const char* fmt) TextWidget_SetConst(&s->desc, "&ePlease enter a filename", &s->textFont); return; } + String_InitArray(path, pathBuffer); - String_Format1(&path, fmt, &file); - String_Copy(&World.Name, &file); + String_Format1(&path, "maps/%s.cw", &file); + String_Copy(&World.Name, &file); if (File_Exists(&path) && !btn->optName) { btn->optName = ""; @@ -1385,48 +1366,38 @@ static void SaveLevelScreen_DoSave(void* screen, void* widget, const char* fmt) } SaveLevelScreen_RemoveOverwrites(s); - if ((res = SaveLevelScreen_SaveMap(s, &path))) return; - -#ifdef CC_BUILD_WEB - if (btn == &s->save) { - Chat_Add1("&eSaved map to: %s", &path); - } else { - DownloadMap(&path); - } -#else + if ((res = SaveLevelScreen_SaveMap(&path))) return; Chat_Add1("&eSaved map to: %s", &path); -#endif } -static void SaveLevelScreen_Save(void* a, void* b) { - SaveLevelScreen_DoSave(a, b, "maps/%s.cw"); -} - -#ifdef CC_BUILD_WEB -static void SaveLevelScreen_File(void* a, void* b) { - SaveLevelScreen_DoSave(a, b, "/tmpmaps/%s.cw"); -} -#else static void SaveLevelScreen_UploadCallback(const cc_string* path) { - cc_result res = SaveLevelScreen_SaveMap(NULL, path); + cc_result res = SaveLevelScreen_SaveMap(path); if (!res) Chat_Add1("&eSaved map to: %s", path); } -static void SaveLevelScreen_File(void* a, void* b) { +static void SaveLevelScreen_File(void* screen, void* b) { static const char* const titles[] = { "ClassiCube map", "MineCraft schematic", "MineCraft classic map", NULL }; static const char* const filters[] = { ".cw", ".schematic", ".mine", NULL }; - static struct SaveFileDialogArgs args = { - filters, titles, SaveLevelScreen_UploadCallback - }; + struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; + struct SaveFileDialogArgs args; + cc_result res; - cc_result res = Window_SaveFileDialog(&args); - if (res) Logger_SimpleWarn(res, "showing save file dialog"); + args.filters = filters; + args.titles = titles; + args.defaultName = s->input.base.text; + args.Callback = SaveLevelScreen_UploadCallback; + + res = Window_SaveFileDialog(&args); + if (res == SFD_ERR_NEED_DEFAULT_NAME) { + TextWidget_SetConst(&s->desc, "&ePlease enter a filename", &s->textFont); + } else if (res) { + Logger_SimpleWarn(res, "showing save file dialog"); + } } -#endif static int SaveLevelScreen_KeyPress(void* screen, char keyChar) { struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen; diff --git a/src/Window.h b/src/Window.h index 05134fdb2..f85d2438a 100644 --- a/src/Window.h +++ b/src/Window.h @@ -136,6 +136,7 @@ typedef void (*FileDialogCallback)(const cc_string* path); struct SaveFileDialogArgs { const char* const* filters; /* File extensions to limit dialog to showing (e.g. ".zip", NULL) */ const char* const* titles; /* Descriptions to show for each file extension */ + cc_string defaultName; /* Default filename (without extension), required by some backends */ FileDialogCallback Callback; }; struct OpenFileDialogArgs { diff --git a/src/Window_Web.c b/src/Window_Web.c index 386e82966..87b32534b 100644 --- a/src/Window_Web.c +++ b/src/Window_Web.c @@ -573,6 +573,24 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { return 0; } +extern int interop_DownloadFile(const char* path, const char* filename); +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + cc_string path; char pathBuffer[FILENAME_SIZE]; + cc_string file; char fileBuffer[FILENAME_SIZE]; + + if (!args->defaultName.length) return SFD_ERR_NEED_DEFAULT_NAME; + + String_InitArray(file, fileBuffer); + String_InitArray(path, pathBuffer); + String_Format2(&file, "%s%c", &args->defaultName, args->filters[0]); + String_Format1(&path, "Downloads/%s", &file); + + args->Callback(&path); + pathBuffer[path.length] = '\0'; + fileBuffer[file.length] = '\0'; + return interop_DownloadFile(pathBuffer, fileBuffer); +} + void Window_AllocFramebuffer(struct Bitmap* bmp) { } void Window_DrawFramebuffer(Rect2D r) { } void Window_FreeFramebuffer(struct Bitmap* bmp) { } diff --git a/src/interop_web.js b/src/interop_web.js index e9bccd9ba..ea16794f4 100644 --- a/src/interop_web.js +++ b/src/interop_web.js @@ -93,7 +93,7 @@ mergeInto(LibraryManager.library, { //######################################################################################################################## //-----------------------------------------------------------Menu--------------------------------------------------------- //######################################################################################################################## - interop_DownloadMap: function(path, filename) { + interop_DownloadFile: function(path, filename) { try { var name = UTF8ToString(path); var data = CCFS.readFile(name); From 75f00743ff62dcd17eacd68c43ffa7a29a789d7a Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 12 Nov 2022 18:13:47 +1100 Subject: [PATCH 09/16] iOS: Implement saving map to external provider (e.g. iCloud) --- src/EntityComponents.c | 1 - src/Formats.c | 17 +++++++------- src/Window_Web.c | 1 + src/interop_ios.m | 53 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/EntityComponents.c b/src/EntityComponents.c index a8d55ee87..9dbe988b8 100644 --- a/src/EntityComponents.c +++ b/src/EntityComponents.c @@ -462,7 +462,6 @@ void LocalInterpComp_SetLocation(struct InterpComp* interp, struct LocationUpdat struct EntityLocation* next = &e->next; cc_uint8 flags = update->flags; cc_bool interpolate = flags & LU_ORI_INTERPOLATE; - float yOffset; if (flags & LU_HAS_POS) { LocalInterpComp_SetPosition(update, flags & LU_POS_MODEMASK); diff --git a/src/Formats.c b/src/Formats.c index 3dc90003e..f7bee774c 100644 --- a/src/Formats.c +++ b/src/Formats.c @@ -1542,8 +1542,7 @@ cc_result Cw_Save(struct Stream* stream) { /*########################################################################################################################* *---------------------------------------------------Schematic export------------------------------------------------------* *#########################################################################################################################*/ - -static cc_uint8 sc_begin[78] = { +static cc_uint8 sc_begin[] = { NBT_DICT, 0,9, 'S','c','h','e','m','a','t','i','c', NBT_STR, 0,9, 'M','a','t','e','r','i','a','l','s', 0,7, 'C','l','a','s','s','i','c', NBT_I16, 0,5, 'W','i','d','t','h', 0,0, @@ -1551,10 +1550,10 @@ NBT_DICT, 0,9, 'S','c','h','e','m','a','t','i','c', NBT_I16, 0,6, 'L','e','n','g','t','h', 0,0, NBT_I8S, 0,6, 'B','l','o','c','k','s', 0,0,0,0, }; -static cc_uint8 sc_data[11] = { +static cc_uint8 sc_data[] = { NBT_I8S, 0,4, 'D','a','t','a', 0,0,0,0, }; -static cc_uint8 sc_end[37] = { +static cc_uint8 sc_end[] = { NBT_LIST, 0,8, 'E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0, NBT_LIST, 0,12, 'T','i','l','e','E','n','t','i','t','i','e','s', NBT_DICT, 0,0,0,0, NBT_END, @@ -1588,6 +1587,10 @@ cc_result Schematic_Save(struct Stream* stream) { return Stream_Write(stream, sc_end, sizeof(sc_end)); } + +/*########################################################################################################################* +*------------------------------------------------------Dat export---------------------------------------------------------* +*#########################################################################################################################*/ static const struct JField { cc_uint8 type, isFloat; const char* name; @@ -1649,10 +1652,8 @@ static cc_result WriteClassDesc(struct Stream* stream, cc_uint8 typecode, const cc_result Dat_Save(struct Stream* stream) { static const cc_uint8 header[] = { - /* DAT signature + version */ - 0x27,0x1B,0xB7,0x88, 0x02, - /* JSF signature + version */ - 0xAC,0xED, 0x00,0x05 + 0x27,0x1B,0xB7,0x88, 0x02, /* DAT signature + version */ + 0xAC,0xED, 0x00,0x05 /* JSF signature + version */ }; const struct JField* field; cc_uint8 tmp[4]; diff --git a/src/Window_Web.c b/src/Window_Web.c index 87b32534b..bd03d2ba0 100644 --- a/src/Window_Web.c +++ b/src/Window_Web.c @@ -586,6 +586,7 @@ cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { String_Format1(&path, "Downloads/%s", &file); args->Callback(&path); + /* TODO use utf8 instead */ pathBuffer[path.length] = '\0'; fileBuffer[file.length] = '\0'; return interop_DownloadFile(pathBuffer, fileBuffer); diff --git a/src/interop_ios.m b/src/interop_ios.m index b0081383d..938c7cd99 100644 --- a/src/interop_ios.m +++ b/src/interop_ios.m @@ -108,7 +108,20 @@ static CGRect GetViewFrame(void) { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; } -static OpenFileDialogCallback open_dlg_callback; +// ==== UIDocumentPickerDelegate ==== +static FileDialogCallback open_dlg_callback; +static char save_buffer[FILENAME_SIZE]; +static cc_string save_path = String_FromArray(save_buffer); + +static void DeleteExportTempFile(void) { + if (!save_path.length) return; + + char path[NATIVE_STR_LEN]; + Platform_EncodeUtf8(path, &save_path); + unlink(path); + save_path.length = 0; +} + - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url { NSString* str = url.path; const char* utf8 = str.UTF8String; @@ -116,7 +129,15 @@ static OpenFileDialogCallback open_dlg_callback; char tmpBuffer[NATIVE_STR_LEN]; cc_string tmp = String_FromArray(tmpBuffer); String_AppendUtf8(&tmp, utf8, String_Length(utf8)); + + DeleteExportTempFile(); + if (!open_dlg_callback) return; open_dlg_callback(&tmp); + open_dlg_callback = NULL; +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { + DeleteExportTempFile(); } static cc_bool kb_active; @@ -514,6 +535,29 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { return 0; // TODO still unfinished } +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { + if (!args->defaultName.length) return SFD_ERR_NEED_DEFAULT_NAME; + + // save the item to a temp file, which is then (usually) later deleted by picker callbacks + cc_string tmpDir = String_FromConst("Exported"); + Directory_Create(&tmpDir); + + save_path.length = 0; + String_Format3(&save_path, "%s/%s%c", &tmpDir, &args->defaultName, args->filters[0]); + args->Callback(&save_path); + + NSString* str = ToNSString(&save_path); + NSURL* url = [NSURL fileURLWithPath:str isDirectory:NO]; + + UIDocumentPickerViewController* dlg; + dlg = [UIDocumentPickerViewController alloc]; + dlg = [dlg initWithURL:url inMode:UIDocumentPickerModeExportToService]; + + dlg.delegate = cc_controller; + [cc_controller presentViewController:dlg animated:YES completion: Nil]; + return 0; +} + /*#########################################################################################################################* *--------------------------------------------------------2D window--------------------------------------------------------* @@ -676,11 +720,8 @@ static char gameArgs[GAME_MAX_CMDARGS][STRING_SIZE]; static int gameNumArgs; cc_result Process_StartOpen(const cc_string* args) { - NSURL* url; - NSString* str; - - str = ToNSString(args); - url = [[NSURL alloc] initWithString:str]; + NSString* str = ToNSString(args); + NSURL* url = [[NSURL alloc] initWithString:str]; [UIApplication.sharedApplication openURL:url]; return 0; } From 69090c8bae16c1eee3aa9273b3ed06de68d637f4 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 12 Nov 2022 23:17:01 +1100 Subject: [PATCH 10/16] Android: WIP on save file dialog support Also fix .mine importer to convert blocks > stone brick to Stone --- .../java/com/classicube/MainActivity.java | 78 +++++++++++++++++-- src/Formats.c | 27 ++++++- src/Window_Android.c | 32 +++++++- src/World.h | 5 +- 4 files changed, 130 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/com/classicube/MainActivity.java b/android/app/src/main/java/com/classicube/MainActivity.java index 2f96cff03..59446c649 100644 --- a/android/app/src/main/java/com/classicube/MainActivity.java +++ b/android/app/src/main/java/com/classicube/MainActivity.java @@ -1,5 +1,6 @@ package com.classicube; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -7,6 +8,7 @@ import java.io.OutputStream; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.Queue; @@ -824,7 +826,10 @@ public class MainActivity extends Activity } - static String uploadFolder; + static String uploadFolder, savePath; + final static int OPEN_REQUEST = 0x4F50454E; + final static int SAVE_REQUEST = 0x53415645; + // https://stackoverflow.com/questions/36557879/how-to-use-native-android-file-open-dialog // https://developer.android.com/guide/topics/providers/document-provider // https://developer.android.com/training/data-storage/shared/documents-files#java @@ -837,7 +842,27 @@ public class MainActivity extends Activity .setType("*/*") .setAction(Intent.ACTION_GET_CONTENT); - startActivityForResult(Intent.createChooser(intent, "Select a file"), 0x55530200); + startActivityForResult(Intent.createChooser(intent, "Select a file"), OPEN_REQUEST); + } catch (Exception ex) { + ex.printStackTrace(); + return 0;// TODO log error to in-game + } + return 1; + } + + // https://stackoverflow.com/questions/8586691/how-to-open-file-save-dialog-in-android + public int saveFileDialog(String path, String name) { + savePath = path; + + try { + Intent intent = new Intent() + .setType("/") + .addCategory(Intent.CATEGORY_OPENABLE) + .setAction(Intent.ACTION_CREATE_DOCUMENT) + .setType("application/octet-stream") + .putExtra(Intent.EXTRA_TITLE, name); + + startActivityForResult(Intent.createChooser(intent, "Choose destination"), SAVE_REQUEST); } catch (Exception ex) { ex.printStackTrace(); return 0;// TODO log error to in-game @@ -848,8 +873,42 @@ public class MainActivity extends Activity @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode != 0x55530200 || resultCode != RESULT_OK) return; + if (resultCode != RESULT_OK) return; + if (requestCode == OPEN_REQUEST) { + handleOpenResult(data); + } else if (requestCode == SAVE_REQUEST) { + handleSaveResult(data); + } + } + + void handleSaveResult(Intent data) { + try { + Uri selected = data.getData(); + saveTempToContent(selected, savePath); + } catch (Exception ex) { + ex.printStackTrace(); + // TODO log error to in-game + } + } + + void saveTempToContent(Uri uri, String path) throws IOException { + File file = new File(getGameDataDirectory() + "/" + path); + OutputStream output = null; + InputStream input = null; + + try { + input = new FileInputStream(file); + output = getContentResolver().openOutputStream(uri); + copyStreamData(input, output); + file.delete(); + } finally { + if (output != null) output.close(); + if (input != null) input.close(); + } + } + + void handleOpenResult(Intent data) { try { Uri selected = data.getData(); String name = getContentFilename(selected); @@ -881,11 +940,7 @@ public class MainActivity extends Activity try { output = new FileOutputStream(file); input = getContentResolver().openInputStream(uri); - - byte[] temp = new byte[8192]; - int length; - while ((length = input.read(temp)) > 0) - output.write(temp, 0, length); + copyStreamData(input, output); } finally { if (output != null) output.close(); if (input != null) input.close(); @@ -893,6 +948,13 @@ public class MainActivity extends Activity return file.getAbsolutePath(); } + static void copyStreamData(InputStream input, OutputStream output) throws IOException { + byte[] temp = new byte[8192]; + int length; + while ((length = input.read(temp)) > 0) + output.write(temp, 0, length); + } + // ====================================================================== // -------------------------------- HTTP -------------------------------- diff --git a/src/Formats.c b/src/Formats.c index f7bee774c..2464e7d6e 100644 --- a/src/Formats.c +++ b/src/Formats.c @@ -1650,6 +1650,31 @@ static cc_result WriteClassDesc(struct Stream* stream, cc_uint8 typecode, const return 0; } +#define DAT_BUFFER_SIZE (64 * 1024) +static cc_result WriteLevelBlocks(struct Stream* stream) { + cc_uint8 buffer[DAT_BUFFER_SIZE]; + int i, bIndex = 0; + cc_result res; + BlockID b; + + for (i = 0; i < World.Volume; i++) + { + b = World_GetRawBlock(i); + /* TODO: Better fallback decision (e.g. air if custom block is 'gas' type) */ + if (b > BLOCK_STONE_BRICK) b = BLOCK_STONE; + + buffer[bIndex] = (cc_uint8)b; + bIndex++; + if (bIndex < DAT_BUFFER_SIZE) continue; + + if ((res = Stream_Write(stream, buffer, DAT_BUFFER_SIZE))) return res; + bIndex = 0; + } + + if (bIndex == 0) return 0; + return Stream_Write(stream, buffer, bIndex); +} + cc_result Dat_Save(struct Stream* stream) { static const cc_uint8 header[] = { 0x27,0x1B,0xB7,0x88, 0x02, /* DAT signature + version */ @@ -1677,7 +1702,7 @@ cc_result Dat_Save(struct Stream* stream) { if ((res = WriteClassDesc(stream, TC_ARRAY, "[B", 0, NULL))) return res; Stream_SetU32_BE(tmp, World.Volume); if ((res = Stream_Write(stream, tmp, 4))) return res; - if ((res = Stream_Write(stream, World.Blocks, World.Volume))) return res; + if ((res = WriteLevelBlocks(stream))) return res; } } return 0; diff --git a/src/Window_Android.c b/src/Window_Android.c index ca144bdc0..0a805b579 100644 --- a/src/Window_Android.c +++ b/src/Window_Android.c @@ -14,7 +14,8 @@ static ANativeWindow* win_handle; static cc_bool winCreated; static jmethodID JAVA_openKeyboard, JAVA_setKeyboardText, JAVA_closeKeyboard; static jmethodID JAVA_getWindowState, JAVA_enterFullscreen, JAVA_exitFullscreen; -static jmethodID JAVA_showAlert, JAVA_setRequestedOrientation, JAVA_openFileDialog; +static jmethodID JAVA_showAlert, JAVA_setRequestedOrientation; +static jmethodID JAVA_openFileDialog, JAVA_saveFileDialog; static jmethodID JAVA_processedSurfaceDestroyed, JAVA_processEvents; static jmethodID JAVA_getDpiX, JAVA_getDpiY, JAVA_setupForGame; @@ -245,6 +246,7 @@ static void CacheMethodRefs(JNIEnv* env) { JAVA_showAlert = JavaGetIMethod(env, "showAlert", "(Ljava/lang/String;Ljava/lang/String;)V"); JAVA_setRequestedOrientation = JavaGetIMethod(env, "setRequestedOrientation", "(I)V"); JAVA_openFileDialog = JavaGetIMethod(env, "openFileDialog", "(Ljava/lang/String;)I"); + JAVA_saveFileDialog = JavaGetIMethod(env, "saveFileDialog", "(Ljava/lang/String;Ljava/lang/String;)I"); } // TODO move to bottom of file? @@ -365,7 +367,7 @@ static void ShowDialogCore(const char* title, const char* msg) { (*env)->DeleteLocalRef(env, args[1].l); } -static OpenFileDialogCallback ofd_callback; +static FileDialogCallback ofd_callback; static int ofd_action; static void JNICALL java_processOFDResult(JNIEnv* env, jobject o, jstring str) { const char* raw; @@ -399,6 +401,32 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* open_args) { return OK ? 0 : ERR_INVALID_ARGUMENT; } +cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* save_args) { + JNIEnv* env; + jvalue args[2]; + JavaGetCurrentEnv(env); + if (!save_args->defaultName.length) return SFD_ERR_NEED_DEFAULT_NAME; + + // save the item to a temp file, which is then (usually) later deleted by intent callback + cc_string tmpDir = String_FromConst("Exported"); + Directory_Create(&tmpDir); + + cc_string path; char pathBuffer[FILENAME_SIZE]; + String_InitArray(path, pathBuffer); + String_Format3(&path, "%s/%s%c", &tmpDir, &save_args->defaultName, save_args->filters[0]); + save_args->Callback(&path); + // TODO kinda ugly, maybe a better way? + cc_string file = String_UNSAFE_SubstringAt(&path, String_IndexOf(&path, '/') + 1); + + args[0].l = JavaMakeString(env, &path); + args[1].l = JavaMakeString(env, &file); + int OK = JavaICall_Int(env, JAVA_saveFileDialog, args); + (*env)->DeleteLocalRef(env, args[0].l); + (*env)->DeleteLocalRef(env, args[1].l); + // TODO: Better error handling + return OK ? 0 : ERR_INVALID_ARGUMENT; +} + static struct Bitmap fb_bmp; void Window_AllocFramebuffer(struct Bitmap* bmp) { bmp->scan0 = (BitmapCol*)Mem_Alloc(bmp->width * bmp->height, 4, "window pixels"); diff --git a/src/World.h b/src/World.h index 190ebf3df..225017219 100644 --- a/src/World.h +++ b/src/World.h @@ -77,14 +77,17 @@ void World_OutOfMemory(void); /* Sets World.Blocks2 and updates internal state for more than 256 blocks. */ void World_SetMapUpper(BlockRaw* blocks); +#define World_GetRawBlock(idx) ((World.Blocks[idx] | (World.Blocks2[idx] << 8)) & World.IDMask) + /* Gets the block at the given coordinates. */ /* NOTE: Does NOT check that the coordinates are inside the map. */ static CC_INLINE BlockID World_GetBlock(int x, int y, int z) { int i = World_Pack(x, y, z); - return (BlockID)((World.Blocks[i] | (World.Blocks2[i] << 8)) & World.IDMask); + return (BlockID)World_GetRawBlock(i); } #else #define World_GetBlock(x, y, z) World.Blocks[World_Pack(x, y, z)] +#define World_GetRawBlock(idx) World.Blocks[idx] #endif /* If Y is above the map, returns BLOCK_AIR. */ From 680d739e6b91169887a711f86f1d311f7c90fc24 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 13 Nov 2022 10:23:42 +1100 Subject: [PATCH 11/16] Add default file extension for zenity file dialog result if none is provided, also reduce width of save map input field so that it fits better on vertical phone orientation --- src/Menus.c | 4 ++-- src/Window_X11.c | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Menus.c b/src/Menus.c index b9102bc40..13779614d 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -1377,7 +1377,7 @@ static void SaveLevelScreen_UploadCallback(const cc_string* path) { static void SaveLevelScreen_File(void* screen, void* b) { static const char* const titles[] = { - "ClassiCube map", "MineCraft schematic", "MineCraft classic map", NULL + "ClassiCube map", "Minecraft schematic", "Minecraft classic map", NULL }; static const char* const filters[] = { ".cw", ".schematic", ".mine", NULL @@ -1476,7 +1476,7 @@ static void SaveLevelScreen_Init(void* screen) { ButtonWidget_Init(&s->file, 400, SaveLevelScreen_File); ButtonWidget_Init(&s->cancel, 400, Menu_SwitchPause); - TextInputWidget_Create(&s->input, 500, &World.Name, &desc); + TextInputWidget_Create(&s->input, 400, &World.Name, &desc); TextWidget_Init(&s->desc); s->input.onscreenPlaceholder = "Map name"; } diff --git a/src/Window_X11.c b/src/Window_X11.c index a054ffdc5..49ccfc25d 100644 --- a/src/Window_X11.c +++ b/src/Window_X11.c @@ -967,7 +967,7 @@ static void ShowDialogCore(const char* title, const char* msg) { XFlush(m.dpy); /* flush so window disappears immediately */ } -static cc_result OpenSaveFileDialog(const char* args, FileDialogCallback callback) { +static cc_result OpenSaveFileDialog(const char* args, FileDialogCallback callback, const char* defaultExt) { cc_string path; char pathBuffer[1024]; char result[4096] = { 0 }; int len, i; @@ -977,14 +977,21 @@ static cc_result OpenSaveFileDialog(const char* args, FileDialogCallback callbac /* result from zenity is normally just one string */ while (fgets(result, sizeof(result), fp)) { } - len = String_Length(result); - - if (len) { - String_InitArray(path, pathBuffer); - String_AppendUtf8(&path, result, len); - callback(&path); - } pclose(fp); + + len = String_Length(result); + if (!len) return 0; + + String_InitArray(path, pathBuffer); + String_AppendUtf8(&path, result, len); + + /* Add default file extension if necessary */ + if (defaultExt) { + cc_string file = path; + Utils_UNSAFE_GetFilename(&file); + if (String_IndexOf(&file, '.') == -1) String_AppendConst(&path, defaultExt); + } + callback(&path); return 0; } @@ -1010,7 +1017,7 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { String_AppendConst(&path, "'"); path.buffer[path.length] = '\0'; - return OpenSaveFileDialog(path.buffer, args->Callback); + return OpenSaveFileDialog(path.buffer, args->Callback, NULL); } cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { @@ -1028,7 +1035,7 @@ cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { String_AppendConst(&path, " --save --confirm-overwrite"); path.buffer[path.length] = '\0'; - return OpenSaveFileDialog(path.buffer, args->Callback); + return OpenSaveFileDialog(path.buffer, args->Callback, fileExts[0]); } static GC fb_gc; From 04ce3daeacc335e962339dcc7cab1861d126d42c Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 13 Nov 2022 12:01:23 +1100 Subject: [PATCH 12/16] mine exporter: Don't forget to convert CPE blocks to their fallbacks --- src/Formats.c | 8 ++++++++ src/Program.c | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Formats.c b/src/Formats.c index 2464e7d6e..55d858327 100644 --- a/src/Formats.c +++ b/src/Formats.c @@ -1650,6 +1650,12 @@ static cc_result WriteClassDesc(struct Stream* stream, cc_uint8 typecode, const return 0; } +static const cc_uint8 cpe_fallback[] = { + BLOCK_SLAB, BLOCK_BROWN_SHROOM, BLOCK_SAND, BLOCK_AIR, BLOCK_STILL_LAVA, BLOCK_PINK, + BLOCK_GREEN, BLOCK_DIRT, BLOCK_BLUE, BLOCK_CYAN, BLOCK_GLASS, BLOCK_IRON, BLOCK_OBSIDIAN, BLOCK_WHITE, + BLOCK_WOOD, BLOCK_STONE +}; + #define DAT_BUFFER_SIZE (64 * 1024) static cc_result WriteLevelBlocks(struct Stream* stream) { cc_uint8 buffer[DAT_BUFFER_SIZE]; @@ -1662,6 +1668,8 @@ static cc_result WriteLevelBlocks(struct Stream* stream) { b = World_GetRawBlock(i); /* TODO: Better fallback decision (e.g. air if custom block is 'gas' type) */ if (b > BLOCK_STONE_BRICK) b = BLOCK_STONE; + /* TODO: Move to GameVersion.c and account for game version */ + if (b > BLOCK_OBSIDIAN) b = cpe_fallback[b - BLOCK_COBBLE_SLAB]; buffer[bIndex] = (cc_uint8)b; bIndex++; diff --git a/src/Program.c b/src/Program.c index b8afce155..3b2105220 100644 --- a/src/Program.c +++ b/src/Program.c @@ -75,8 +75,8 @@ static int RunProgram(int argc, char** argv) { #ifdef _MSC_VER /* NOTE: Make sure to comment this out before pushing a commit */ //cc_string rawArgs = String_FromConst("UnknownShadow200 fffff 127.0.0.1 25565"); - cc_string rawArgs = String_FromConst("UnknownShadow200"); - argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4); + //cc_string rawArgs = String_FromConst("UnknownShadow200"); + //argsCount = String_UNSAFE_Split(&rawArgs, ' ', args, 4); #endif if (argsCount == 0) { From e1ee492cc84a847aa548bdddb5fab95847621e4e Mon Sep 17 00:00:00 2001 From: cflip Date: Sun, 13 Nov 2022 14:42:59 -0700 Subject: [PATCH 13/16] Implement missing PrintRegisters function for SerenityOS --- src/Logger.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Logger.c b/src/Logger.c index 29fae7d9a..84fcfb95c 100644 --- a/src/Logger.c +++ b/src/Logger.c @@ -767,6 +767,19 @@ static void PrintRegisters(cc_string* str, void* ctx) { #error "Unknown CPU architecture" #endif } +#elif defined CC_BUILD_SERENITY +static void PrintRegisters(cc_string* str, void* ctx) { + mcontext_t r = ((ucontext_t*)ctx)->uc_mcontext; +#if defined __i386__ + #define REG_GET(reg, ign) &r.e##reg + Dump_X86() +#elif defined __x86_64__ + #define REG_GET(reg, ign) &r.r##reg + Dump_X64() +#else + #error "Unknown CPU architecture" +#endif +} #endif static void DumpRegisters(void* ctx) { From ca41fe5a820da579dd2427afb7e230c79c9da8b8 Mon Sep 17 00:00:00 2001 From: cflip Date: Sun, 13 Nov 2022 15:21:44 -0700 Subject: [PATCH 14/16] Add resource and library paths for SerenityOS --- src/Drawer2D.c | 1 + src/Http_Worker.c | 3 +++ src/Platform_Posix.c | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/Drawer2D.c b/src/Drawer2D.c index 401e40cd9..b066c2395 100644 --- a/src/Drawer2D.c +++ b/src/Drawer2D.c @@ -818,6 +818,7 @@ static cc_string font_candidates[] = { String_FromConst("Cantarell"), String_FromConst("DejaVu Sans Book"), String_FromConst("Century Schoolbook L Roman"), /* commonly available on linux */ + String_FromConst("Liberation Serif"), /* for SerenityOS */ 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 */ diff --git a/src/Http_Worker.c b/src/Http_Worker.c index 936361eb0..514494fc5 100644 --- a/src/Http_Worker.c +++ b/src/Http_Worker.c @@ -254,6 +254,9 @@ static const cc_string curlAlt = String_FromConst("/usr/pkg/lib/libcurl.so"); #elif defined CC_BUILD_BSD static const cc_string curlLib = String_FromConst("libcurl.so"); static const cc_string curlAlt = String_FromConst("libcurl.so"); +#elif defined CC_BUILD_SERENITY +static const cc_string curlLib = String_FromConst("/usr/local/lib/libcurl.so"); +static const cc_string curlAlt = String_FromConst("/usr/local/lib/libcurl.so"); #else static const cc_string curlLib = String_FromConst("libcurl.so.4"); static const cc_string curlAlt = String_FromConst("libcurl.so.3"); diff --git a/src/Platform_Posix.c b/src/Platform_Posix.c index cf7171aa6..38baa5fd7 100644 --- a/src/Platform_Posix.c +++ b/src/Platform_Posix.c @@ -459,6 +459,10 @@ void Platform_LoadSysFonts(void) { String_FromConst("/System/Library/Fonts"), String_FromConst("/Library/Fonts") }; +#elif defined CC_BUILD_SERENITY + static const cc_string dirs[] = { + String_FromConst("/res/fonts") + }; #else static const cc_string dirs[] = { String_FromConst("/usr/share/fonts"), From 9bbea3a4b78408a5bc2d8ca2a39022895b921e21 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Mon, 14 Nov 2022 18:52:14 +1100 Subject: [PATCH 15/16] Rename /rendertype normalfast to just fast, and include better help text normalfast continues to work for backwards compatibility though --- src/Chat.c | 10 +++++----- src/EnvRenderer.c | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Chat.c b/src/Chat.c index 4d5dcea30..52a377a30 100644 --- a/src/Chat.c +++ b/src/Chat.c @@ -434,11 +434,11 @@ static struct ChatCommand RenderTypeCommand = { "RenderType", RenderTypeCommand_Execute, COMMAND_FLAG_UNSPLIT_ARGS, { - "&a/client rendertype [normal/legacy/legacyfast]", - "&bnormal: &eDefault renderer, with all environmental effects enabled.", - "&blegacy: &eMay be slightly slower than normal, but produces the same environmental effects.", - "&blegacyfast: &eSacrifices clouds, fog and overhead sky for faster performance.", - "&bnormalfast: &eSacrifices clouds, fog and overhead sky for faster performance.", + "&a/client rendertype [normal/legacy/fast]", + "&bnormal: &eDefault render mode, with all environmental effects enabled", + "&blegacy: &eSame as normal mode, &cbut is usually slightly slower", + " &eIf you have issues with clouds and map edges disappearing randomly, use this mode", + "&bfast: &eSacrifices clouds, fog and overhead sky for faster performance", } }; diff --git a/src/EnvRenderer.c b/src/EnvRenderer.c index 58b0b2e43..c4a71d91f 100644 --- a/src/EnvRenderer.c +++ b/src/EnvRenderer.c @@ -811,14 +811,16 @@ static void OnContextRecreated(void* obj) { void EnvRenderer_SetMode(int flags) { EnvRenderer_Legacy = flags & ENV_LEGACY; EnvRenderer_Minimal = flags & ENV_MINIMAL; - OnContextRecreated(NULL); + OnContextRecreated(NULL); } int EnvRenderer_CalcFlags(const cc_string* mode) { - if (String_CaselessEqualsConst(mode, "legacyfast")) return ENV_LEGACY | ENV_MINIMAL; - if (String_CaselessEqualsConst(mode, "legacy")) return ENV_LEGACY; - if (String_CaselessEqualsConst(mode, "normal")) return 0; + if (String_CaselessEqualsConst(mode, "normal")) return 0; + if (String_CaselessEqualsConst(mode, "legacy")) return ENV_LEGACY; + if (String_CaselessEqualsConst(mode, "fast")) return ENV_MINIMAL; + /* backwards compatibility */ if (String_CaselessEqualsConst(mode, "normalfast")) return ENV_MINIMAL; + if (String_CaselessEqualsConst(mode, "legacyfast")) return ENV_LEGACY | ENV_MINIMAL; return -1; } From 2e04505df0957d0d6806fcb301dc86a25a1cf519 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Tue, 15 Nov 2022 22:30:49 +1100 Subject: [PATCH 16/16] Save file dialog on Windows and Linux now also defaults to showing text entered in input save menu input field, also fallback to reading from /etc/machine-id for getting machine ID on Linux for e.g. Flatpaks --- src/EnvRenderer.c | 2 +- src/Platform_Posix.c | 11 ++++++++--- src/Window_Win.c | 9 +++++---- src/Window_X11.c | 6 ++++++ src/interop_cocoa.m | 2 ++ 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/EnvRenderer.c b/src/EnvRenderer.c index c4a71d91f..8b685c8b0 100644 --- a/src/EnvRenderer.c +++ b/src/EnvRenderer.c @@ -811,7 +811,7 @@ static void OnContextRecreated(void* obj) { void EnvRenderer_SetMode(int flags) { EnvRenderer_Legacy = flags & ENV_LEGACY; EnvRenderer_Minimal = flags & ENV_MINIMAL; - OnContextRecreated(NULL); + OnContextRecreated(NULL); } int EnvRenderer_CalcFlags(const cc_string* mode) { diff --git a/src/Platform_Posix.c b/src/Platform_Posix.c index 38baa5fd7..a89ffd97c 100644 --- a/src/Platform_Posix.c +++ b/src/Platform_Posix.c @@ -1096,14 +1096,19 @@ static void DecodeMachineID(char* tmp, int len, cc_uint32* key) { } #if defined CC_BUILD_LINUX -/* Read /var/lib/dbus/machine-id for the key */ +/* Read /var/lib/dbus/machine-id or /etc/machine-id for the key */ static cc_result GetMachineID(cc_uint32* key) { - const cc_string idFile = String_FromConst("/var/lib/dbus/machine-id"); + const cc_string idFile = String_FromConst("/var/lib/dbus/machine-id"); + const cc_string altFile = String_FromConst("/etc/machine-id"); char tmp[MACHINEID_LEN]; struct Stream s; cc_result res; - if ((res = Stream_OpenFile(&s, &idFile))) return res; + /* Some machines only have dbus id, others only have etc id */ + res = Stream_OpenFile(&s, &idFile); + if (res) res = Stream_OpenFile(&s, &altFile); + if (res) return res; + res = Stream_Read(&s, tmp, MACHINEID_LEN); if (!res) DecodeMachineID(tmp, MACHINEID_LEN, key); diff --git a/src/Window_Win.c b/src/Window_Win.c index 60dccb151..df2b93448 100644 --- a/src/Window_Win.c +++ b/src/Window_Win.c @@ -551,8 +551,8 @@ static void ShowDialogCore(const char* title, const char* msg) { MessageBoxA(win_handle, msg, title, 0); } -static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback callback, - const char* const* fileExts, cc_bool load) { +static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback callback, cc_bool load, + const char* const* fileExts, const cc_string* defaultName) { cc_string path; char pathBuffer[NATIVE_STR_LEN]; WCHAR str[MAX_PATH] = { 0 }; OPENFILENAMEW ofn = { 0 }; @@ -560,6 +560,7 @@ static cc_result OpenSaveFileDialog(const cc_string* filters, FileDialogCallback BOOL ok; int i; + Platform_EncodeUtf16(str, defaultName); Platform_EncodeUtf16(filter, filters); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = win_handle; @@ -608,7 +609,7 @@ cc_result Window_OpenFileDialog(const struct OpenFileDialogArgs* args) { } String_Append(&filters, '\0'); - return OpenSaveFileDialog(&filters, args->Callback, fileExts, true); + return OpenSaveFileDialog(&filters, args->Callback, true, fileExts, &String_Empty); } cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { @@ -626,7 +627,7 @@ cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { String_Format1(&filters, "*%c", fileExts[i]); String_Append(&filters, '\0'); } - return OpenSaveFileDialog(&filters, args->Callback, fileExts, false); + return OpenSaveFileDialog(&filters, args->Callback, false, fileExts, &args->defaultName); } static HDC draw_DC; diff --git a/src/Window_X11.c b/src/Window_X11.c index 32dbbc1f5..9ce11669e 100644 --- a/src/Window_X11.c +++ b/src/Window_X11.c @@ -6,6 +6,7 @@ #include "Bitmap.h" #include "Options.h" #include "Errors.h" +#include "Utils.h" #include #include #include @@ -1034,6 +1035,11 @@ cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { } String_AppendConst(&path, " --save --confirm-overwrite"); + /* TODO: Utf8 encode filename */ + if (args->defaultName.length) { + String_Format1(&path, " --filename='%s'", &args->defaultName); + } + path.buffer[path.length] = '\0'; return OpenSaveFileDialog(path.buffer, args->Callback, fileExts[0]); } diff --git a/src/interop_cocoa.m b/src/interop_cocoa.m index fcf6c6ac1..c7ab56a31 100644 --- a/src/interop_cocoa.m +++ b/src/interop_cocoa.m @@ -561,6 +561,8 @@ cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) { NSString* str; const char* src; int len, i; + + // TODO: Use args->defaultName, but only macOS 10.6 NSMutableArray* types = GetOpenSaveFilters(args->filters); [dlg setAllowedFileTypes:types];