/* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright (C) 2021 KikooDX */ #include #include #include #include "level.h" static const int kble_fmt_version = 0; static int read_byte(FILE *file); static int read_byte_group(FILE *file, int size); static void write_byte(FILE *file, unsigned char byte); static void write_byte_group(FILE *file, int value, int size); void level_read(struct Level *level, char *path) { FILE *file = NULL; int byte = 0; int tile_size = 0; int level_size = 0; int tile = 0; int i = 0; /* open file in read mode */ file = fopen(path, "rb"); if (file == NULL) { fprintf(stderr, "ERROR: cannot open input file %s\n", path); exit(EXIT_FAILURE); } /* check KBLE format version */ byte = read_byte(file); if (byte != kble_fmt_version) { fprintf(stderr, "ERROR: KBLE format version doesn't match ; " "expected %d, got %d\n", kble_fmt_version, byte); exit(EXIT_FAILURE); } /* get tile size (in bytes) */ tile_size = read_byte(file); /* get width */ level->width = read_byte_group(file, 2); /* get height */ level->height = read_byte_group(file, 2); /* allocate memory for data */ level_size = level->width * level->height; if (level->data == NULL) { level->data = malloc(level_size * sizeof(int)); } else { level->data = realloc(level->data, level_size * sizeof(int)); } /* check for allocation failure */ if (level->data == NULL) { fprintf(stderr, "ERROR: memory allocation failure\n"); exit(EXIT_FAILURE); } /* read file content */ for (i = 0; i < level_size; i += 1) { tile = read_byte_group(file, tile_size); level->data[i] = tile; } /* close file */ fclose(file); } void level_free(struct Level *level) { free(level->data); } void level_write(struct Level level, char *path) { FILE *file = NULL; int tile = 0; int tile_size = 0; int max_tile_size = 1; int i = 0; const int level_size = level.width * level.height; /* open file in write mode */ file = fopen(path, "wb"); if (file == NULL) { fprintf(stderr, "ERROR: cannot open output file %s\n", path); exit(EXIT_FAILURE); } /* find longest value in data (in bytes) */ for (i = 0; i < level_size; i += 1) { tile = level.data[i]; tile_size = 1; while (tile >>= 8) tile_size += 1; if (tile_size > max_tile_size) max_tile_size = tile_size; } /* write KBLE format version */ write_byte(file, kble_fmt_version); /* write tile size (in bytes) */ write_byte(file, max_tile_size); /* write width */ write_byte_group(file, level.width, 2); /* write height */ write_byte_group(file, level.height, 2); /* write level content */ for (i = 0; i < level_size; i += 1) { write_byte_group(file, level.data[i], max_tile_size); } /* close file */ fclose(file); } /* Read a single byte safely (handle EOF). */ static int read_byte(FILE *file) { const int byte = fgetc(file); if (byte == EOF) { fprintf(stderr, "ERROR: unexpected EOF\n"); exit(EXIT_FAILURE); } return byte; } /* Read multiple bytes and "merge" them into one integer. */ static int read_byte_group(FILE *file, int size) { int byte = 0; int merged = 0; int i = 0; int shift = size * 8; for (i = 0; i < size; i += 1) { shift -= 8; byte = read_byte(file); merged |= byte << shift; } return merged; } /* Write a single byte safely (handle EOF). */ static void write_byte(FILE *file, unsigned char byte) { const int result = fputc(byte, file); if (result == EOF) { fprintf(stderr, "ERROR: file write error\n"); exit(EXIT_FAILURE); } } /* Write an integer as multiple bytes. */ static void write_byte_group(FILE *file, int value, int size) { int byte = 0; int shift = size * 8; int i = 0; const int mask = (1 << 8) - 1; for (i = 0; i < size; i += 1) { shift -= 8; byte = value >> shift; byte &= mask; write_byte(file, byte); } }