mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-16 19:15:14 -04:00
Merge branch 'UnknownShadow200:master' into flatpak
This commit is contained in:
commit
fb8691a144
@ -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 --------------------------------
|
||||
|
@ -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
|
||||
|
10
src/Chat.c
10
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",
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -815,10 +815,12 @@ void EnvRenderer_SetMode(int flags) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
136
src/Formats.c
136
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,
|
||||
@ -1587,3 +1586,132 @@ 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;
|
||||
void* value;
|
||||
} level_fields[] = {
|
||||
{ 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
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;
|
||||
/* 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++;
|
||||
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 */
|
||||
0xAC,0xED, 0x00,0x05 /* JSF signature + version */
|
||||
};
|
||||
const struct JField* field;
|
||||
cc_uint8 tmp[4];
|
||||
cc_result res;
|
||||
int i, value;
|
||||
|
||||
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++)
|
||||
{
|
||||
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;
|
||||
Stream_SetU32_BE(tmp, World.Volume);
|
||||
if ((res = Stream_Write(stream, tmp, 4))) return res;
|
||||
if ((res = WriteLevelBlocks(stream))) return res;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -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
|
||||
|
@ -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");
|
||||
|
123
src/Logger.c
123
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
|
||||
@ -674,7 +767,21 @@ 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) {
|
||||
cc_string str; char strBuffer[768];
|
||||
String_InitArray(str, strBuffer);
|
||||
|
@ -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 $@
|
||||
|
184
src/Menus.c
184
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 {
|
||||
@ -1277,154 +1278,125 @@ 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 mcEdit, desc;
|
||||
struct TextWidget desc;
|
||||
} SaveLevelScreen;
|
||||
|
||||
static struct Widget* save_widgets[6] = {
|
||||
(struct Widget*)&SaveLevelScreen.save, (struct Widget*)&SaveLevelScreen.alt,
|
||||
(struct Widget*)&SaveLevelScreen.mcEdit, (struct Widget*)&SaveLevelScreen.cancel,
|
||||
static struct Widget* save_widgets[] = {
|
||||
(struct Widget*)&SaveLevelScreen.save, (struct Widget*)&SaveLevelScreen.file,
|
||||
(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,
|
||||
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
|
||||
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);
|
||||
|
||||
/* maps/aaa.schematic -> aaa.cw */
|
||||
file = *path; Utils_UNSAFE_GetFilename(&file);
|
||||
file.length = String_LastIndexOf(&file, '.');
|
||||
String_AppendConst(&file, ".cw");
|
||||
Platform_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 void SaveLevelScreen_SaveMap(struct SaveLevelScreen* s, const cc_string* path) {
|
||||
static const cc_string cw = String_FromConst(".cw");
|
||||
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;
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
#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) {
|
||||
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;
|
||||
|
||||
if (!file.length) {
|
||||
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 = "";
|
||||
SaveLevelScreen_UpdateSave(s);
|
||||
SaveLevelScreen_UpdateAlt(s);
|
||||
} else {
|
||||
SaveLevelScreen_RemoveOverwrites(s);
|
||||
SaveLevelScreen_SaveMap(s, &path);
|
||||
return;
|
||||
}
|
||||
|
||||
SaveLevelScreen_RemoveOverwrites(s);
|
||||
if ((res = SaveLevelScreen_SaveMap(&path))) return;
|
||||
Chat_Add1("&eSaved map to: %s", &path);
|
||||
}
|
||||
static void SaveLevelScreen_Main(void* a, void* b) { SaveLevelScreen_Save(a, b, "maps/%s.cw"); }
|
||||
#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"); }
|
||||
#else
|
||||
static void SaveLevelScreen_Alt(void* a, void* b) { SaveLevelScreen_Save(a, b, "maps/%s.schematic"); }
|
||||
#endif
|
||||
|
||||
static void SaveLevelScreen_Render(void* screen, double delta) {
|
||||
PackedCol grey = PackedCol_Make(150, 150, 150, 255);
|
||||
int x, y;
|
||||
MenuScreen_Render2(screen, delta);
|
||||
static void SaveLevelScreen_UploadCallback(const cc_string* path) {
|
||||
cc_result res = SaveLevelScreen_SaveMap(path);
|
||||
if (!res) Chat_Add1("&eSaved map to: %s", path);
|
||||
}
|
||||
|
||||
#ifndef CC_BUILD_WEB
|
||||
x = WindowInfo.Width / 2; y = WindowInfo.Height / 2;
|
||||
Gfx_Draw2DFlat(x - 250, y + 90, 500, 2, grey);
|
||||
#endif
|
||||
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
|
||||
};
|
||||
struct SaveLevelScreen* s = (struct SaveLevelScreen*)screen;
|
||||
struct SaveFileDialogArgs args;
|
||||
cc_result res;
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
static int SaveLevelScreen_KeyPress(void* screen, char keyChar) {
|
||||
@ -1466,13 +1438,14 @@ static void SaveLevelScreen_ContextRecreated(void* screen) {
|
||||
|
||||
Screen_UpdateVb(screen);
|
||||
SaveLevelScreen_UpdateSave(s);
|
||||
SaveLevelScreen_UpdateAlt(s);
|
||||
|
||||
#ifndef CC_BUILD_WEB
|
||||
TextWidget_SetConst(&s->mcEdit, "&eCan be imported into MCEdit", &s->textFont);
|
||||
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
|
||||
TextInputWidget_SetFont(&s->input, &s->textFont);
|
||||
ButtonWidget_SetConst(&s->cancel, "Cancel", &s->titleFont);
|
||||
}
|
||||
|
||||
static void SaveLevelScreen_Update(void* screen, double delta) {
|
||||
@ -1482,21 +1455,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);
|
||||
Widget_SetLocation(&s->mcEdit, ANCHOR_CENTRE, ANCHOR_CENTRE, 110, 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) {
|
||||
@ -1508,24 +1472,18 @@ 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);
|
||||
s->widgets[2] = NULL; /* null mcEdit widget */
|
||||
#else
|
||||
ButtonWidget_Init(&s->alt, 200, SaveLevelScreen_Alt);
|
||||
TextWidget_Init(&s->mcEdit);
|
||||
#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);
|
||||
TextInputWidget_Create(&s->input, 400, &World.Name, &desc);
|
||||
TextWidget_Init(&s->desc);
|
||||
s->input.onscreenPlaceholder = "Map name";
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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"),
|
||||
@ -1092,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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
50
src/Server.c
50
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;
|
||||
}
|
||||
|
||||
|
||||
|
58
src/Server.h
58
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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
12
src/Window.h
12
src/Window.h
@ -131,17 +131,25 @@ 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 */
|
||||
cc_string defaultName; /* Default filename (without extension), required by some backends */
|
||||
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. */
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -9,6 +9,8 @@
|
||||
#include <SDL2/SDL.h>
|
||||
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);
|
||||
}
|
||||
@ -281,6 +283,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;
|
||||
|
||||
|
@ -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);
|
||||
@ -573,6 +573,25 @@ 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);
|
||||
/* TODO use utf8 instead */
|
||||
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) { }
|
||||
|
@ -551,52 +551,85 @@ 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,
|
||||
const char* const* fileExts, const cc_string* defaultName) {
|
||||
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(str, defaultName);
|
||||
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);
|
||||
|
||||
/* 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* 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(filters, buffer);
|
||||
String_Format1(&filters, "%c (", args->description);
|
||||
for (i = 0; fileExts[i]; i++)
|
||||
{
|
||||
if (i) String_Append(&filters, ';');
|
||||
String_Format1(&filters, "*%c", fileExts[i]);
|
||||
}
|
||||
String_Append(&filters, ')');
|
||||
String_Append(&filters, '\0');
|
||||
|
||||
for (i = 0; fileExts[i]; i++)
|
||||
{
|
||||
if (i) String_Append(&filters, ';');
|
||||
String_Format1(&filters, "*%c", fileExts[i]);
|
||||
}
|
||||
String_Append(&filters, '\0');
|
||||
|
||||
return OpenSaveFileDialog(&filters, args->Callback, true, fileExts, &String_Empty);
|
||||
}
|
||||
|
||||
cc_result Window_SaveFileDialog(const struct SaveFileDialogArgs* args) {
|
||||
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(filters, buffer);
|
||||
for (i = 0; fileExts[i]; i++)
|
||||
{
|
||||
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(&filters, args->Callback, false, fileExts, &args->defaultName);
|
||||
}
|
||||
|
||||
static HDC draw_DC;
|
||||
static HBITMAP draw_DIB;
|
||||
void Window_AllocFramebuffer(struct Bitmap* bmp) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "Bitmap.h"
|
||||
#include "Options.h"
|
||||
#include "Errors.h"
|
||||
#include "Utils.h"
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/XKBlib.h>
|
||||
@ -251,7 +252,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();
|
||||
|
||||
@ -292,7 +293,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;
|
||||
|
||||
@ -967,12 +968,38 @@ 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, const char* defaultExt) {
|
||||
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)) { }
|
||||
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;
|
||||
}
|
||||
|
||||
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 +1016,32 @@ 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, NULL);
|
||||
}
|
||||
|
||||
/* 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");
|
||||
|
||||
/* 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]);
|
||||
}
|
||||
|
||||
static GC fb_gc;
|
||||
|
@ -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. */
|
||||
|
@ -530,43 +530,72 @@ 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;
|
||||
// TODO: Use args->defaultName, but only macOS 10.6
|
||||
|
||||
files = [dlg URLs];
|
||||
if ([files count] < 1) return 0;
|
||||
NSMutableArray* types = GetOpenSaveFilters(args->filters);
|
||||
[dlg setAllowedFileTypes:types];
|
||||
if ([dlg runModal] != NSOKButton) 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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user