Make randtilegen and rgbgfx_test compile with MSVC

This commit is contained in:
ISSOtm
2022-04-30 20:37:46 +02:00
committed by Eldred Habert
parent 01cf0c5f98
commit 3cfe7800c7
6 changed files with 330 additions and 205 deletions

View File

@@ -29,8 +29,18 @@ option(MORE_WARNINGS "Turn on more warnings" OFF) # Ignored on MSVC
if(MSVC) if(MSVC)
# MSVC's standard library triggers warning C5105, # MSVC's standard library triggers warning C5105,
# "macro expansion producing 'defined' has undefined behavior" # "macro expansion producing 'defined' has undefined behavior"
add_compile_options(/std:c11 /W1 /MP /wd5105) add_compile_options(/MP /wd5105)
add_definitions(/D_CRT_SECURE_NO_WARNINGS) add_definitions(/D_CRT_SECURE_NO_WARNINGS)
# Also, CMake appears not to pass the C11-enabling flag, so we must add it manually... but only for C!
if(NOT CMAKE_C_FLAGS MATCHES "std:c11") # The flag may already have been injected by an earlier CMake invocation, so don't add it twice
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std:c11" CACHE STRING "Flags used by the C compiler during all build types." FORCE)
endif()
if(SANITIZERS)
set(SAN_FLAGS /fsanitize=address)
add_compile_options(${SAN_FLAGS})
add_link_options(${SAN_FLAGS})
endif()
else() else()
add_compile_options(-Wall -pedantic) add_compile_options(-Wall -pedantic)
add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE) add_definitions(-D_POSIX_C_SOURCE=200809L -D_ISOC11_SOURCE)
@@ -41,7 +51,7 @@ else()
-fsanitize=object-size -fsanitize=bool -fsanitize=enum -fsanitize=object-size -fsanitize=bool -fsanitize=enum
-fsanitize=alignment -fsanitize=null -fsanitize=address) -fsanitize=alignment -fsanitize=null -fsanitize=address)
add_compile_options(${SAN_FLAGS}) add_compile_options(${SAN_FLAGS})
link_libraries(${SAN_FLAGS}) add_link_options(${SAN_FLAGS})
# A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless # A non-zero optimization level is desired in debug mode, but allow overriding it nonetheless
# TODO: this overrides anything previously set... that's a bit sloppy! # TODO: this overrides anything previously set... that's a bit sloppy!
set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE) set(CMAKE_C_FLAGS_DEBUG "-g -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE STRING "" FORCE)

View File

@@ -129,7 +129,7 @@ rgbgfx: ${rgbgfx_obj}
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS} $Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ ${rgbgfx_obj} ${REALCXXFLAGS} -x c++ src/version.c ${PNGLDLIBS}
test/gfx/randtilegen: test/gfx/randtilegen.c test/gfx/randtilegen: test/gfx/randtilegen.c
$Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} -Wno-vla ${PNGCFLAGS} ${PNGLDLIBS} $Q${CC} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCFLAGS} ${PNGCFLAGS} ${PNGLDLIBS}
test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp test/gfx/rgbgfx_test: test/gfx/rgbgfx_test.cpp
$Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGLDLIBS} $Q${CXX} ${REALLDFLAGS} ${PNGLDFLAGS} -o $@ $^ ${REALCXXFLAGS} ${PNGLDLIBS}

View File

@@ -46,12 +46,14 @@
# include <unistd.h> # include <unistd.h>
#endif #endif
/* MSVC doesn't support `[static N]` for array arguments from C99 */ /* MSVC doesn't support `[static N]` for array arguments from C99 or C11 */
#ifdef _MSC_VER #ifdef _MSC_VER
# define MIN_NB_ELMS(N) # define MIN_NB_ELMS(N)
# define ARR_QUALS(...)
# define NONNULL(ptr) *ptr # define NONNULL(ptr) *ptr
#else #else
# define MIN_NB_ELMS(N) static (N) # define MIN_NB_ELMS(N) static (N)
# define ARR_QUALS(...) __VA_ARGS__
# define NONNULL(ptr) ptr[static 1] # define NONNULL(ptr) ptr[static 1]
#endif #endif

View File

@@ -1,8 +1,5 @@
add_executable(randtilegen gfx/randtilegen.c) add_executable(randtilegen gfx/randtilegen.c)
if(NOT MSVC)
target_compile_options(randtilegen PRIVATE -Wno-vla)
endif()
add_executable(rgbgfx_test gfx/rgbgfx_test.cpp) add_executable(rgbgfx_test gfx/rgbgfx_test.cpp)

View File

@@ -14,7 +14,7 @@
*/ */
#include <assert.h> #include <assert.h>
#include <inttypes.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include <png.h> #include <png.h>
#include <stdint.h> #include <stdint.h>
@@ -22,41 +22,138 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "helpers.h" #include "platform.h"
FILE *rngRecorder; // File to which the random bytes will be read #define STR(x) #x
uint32_t randBits = 0; // Storage for bits read from the input stream but not yet used #define XSTR(x) STR(x)
uint8_t randCount = 0; // How many bits are currently stored in the above
static uint32_t getRandomBits(uint8_t count) { struct Attributes {
// Trying to read one more byte with `randCount` at least this high will drop some bits! unsigned char palette;
// If the count is no higher than that limit, then the loop is guaranteed to exit without unsigned char nbColors;
// reading more bytes. };
assert(count <= sizeof(randBits) * 8 + 1);
// Read bytes until we have enough bits to serve the request static unsigned long long randbits = 0;
while (count > randCount) { static unsigned char randcount = 0;
int data = getchar();
static _Noreturn void fatal(char const *error) {
fprintf(stderr, "FATAL: %s\n", error);
exit(1);
}
static FILE *seed;
static unsigned long long getRandomBits(unsigned count) {
while (count > randcount) {
// Get new random bytes from stdin (assumed to be a stream of random data) to fulfill the
// random bits request
int data = getc(seed);
if (data == EOF) { if (data == EOF) {
exit(0); exit(0);
} }
randBits |= (uint32_t)data << randCount; randbits |= (unsigned long long)data << randcount;
randCount += 8; randcount += 8;
fputc(data, rngRecorder);
} }
unsigned long long result = randbits & ((1ull << count) - 1);
uint32_t result = randBits & (((uint32_t)1 << count) - 1); randbits >>= count;
randBits >>= count; randcount -= count;
randCount -= count;
return result; return result;
} }
/** static void generate_tile_attributes(struct Attributes * restrict attributes) {
* Flush any remaining bits in the RNG storage /*
*/ * Images have ten colors, grouped into two groups of 5 colors. The palette index indicates two
static void flushRng(void) { * things: which one of those groups will be used, and which colors out of those 5 will be used
randCount = 0; * by the tile. The low bit indicates the group, and the rest of the value indicates the subset
randBits = 0; * of colors. The remainder of the number is treated as a bitfield, where each bit represents a
* color: for instance, a value of 13 in the upper bits (binary 01101) indicates that colors 0,
* 2 and 3 from that group will be used. Values of 0 and 31 are naturally invalid because they
* indicate zero and five colors respectively, and 30 is also excluded to ensure that the
* particular subset of colors 1, 2, 3 and 4 never shows up. This guarantees that every tile
* will be representable using a palette containing color 0 (since those that don't contain
* color 0 will have three colors at most), which in turn ensures that only 4 palettes per group
* (and thus 8 total) are needed to cover the image: 0, 1, 2, 3; 0, 1, 2, 4; 0, 1, 3, 4; and 0,
* 2, 3, 4. This also implies that making color 0 transparent (in both groups) adds a
* transparent color to every palette.
*/
unsigned char pal;
do {
pal = getRandomBits(5);
} while (pal == 0 || (pal > 29));
attributes->palette = 2 * pal + getRandomBits(1);
// Use an array to look up the number of colors in the palette; this is faster (and simpler)
// than doing a population count over the bits
static char const popcount[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3,
4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4};
attributes->nbColors = popcount[pal];
}
static void generate_tile_data(unsigned char tiledata[ARR_QUALS(restrict) MIN_NB_ELMS(8)][8],
unsigned colorcount) {
switch (colorcount) {
case 2: // 1bpp
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
tiledata[y][x] = getRandomBits(1);
}
}
break;
case 4: // 2bpp
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
tiledata[y][x] = getRandomBits(2);
}
}
break;
case 3: // 2bpp with resampling
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
do {
tiledata[y][x] = getRandomBits(2);
} while (tiledata[y][x] == 3);
}
}
}
}
// Can't mark as `const`, as the array type is otherwise not compatible (augh)
static void
copy_tile_data(unsigned char destination[ARR_QUALS(restrict) MIN_NB_ELMS(8)][8],
unsigned char /* const */ source[ARR_QUALS(restrict) MIN_NB_ELMS(8)][8]) {
// Apply a random rotation to the copy
// coord ^ 7 = inverted coordinate; coord ^ 0 = regular coordinate
unsigned xmask = getRandomBits(1) * 7;
unsigned ymask = getRandomBits(1) * 7;
for (unsigned y = 0; y < 8; y++) {
for (unsigned x = 0; x < 8; x++) {
destination[y][x] = source[y ^ ymask][x ^ xmask];
}
}
}
static void generate_palettes(uint16_t palettes[ARR_QUALS(restrict) MIN_NB_ELMS(60)][4]) {
uint16_t colors[10];
// Generate 10 random colors (two groups of 5 colors)
for (unsigned p = 0; p < 10; p++) {
colors[p] = getRandomBits(15);
}
// Potentially make the first color of each group transparent
if (!getRandomBits(2)) {
colors[0] |= 0x8000;
colors[5] |= 0x8000;
}
for (unsigned p = 0; p < 60; p++) {
uint16_t const *group = colors + 5 * (p & 1);
uint16_t *palette = palettes[p];
for (unsigned index = 0; index < 5; index++) {
if (p & (2 << index)) {
*(palette++) = group[index];
}
}
}
} }
/** /**
@@ -66,14 +163,25 @@ static uint8_t _5to8(uint8_t five) {
return five << 3 | five >> 2; return five << 3 | five >> 2;
} }
struct Attribute { // Can't mark as `const`, as the array type is otherwise not compatible (augh)
unsigned char palette; static void write_image(char const *filename, uint16_t /* const */ palettes[MIN_NB_ELMS(60)][4],
unsigned char nbColors; unsigned char /* const */ (*tileData)[8][8],
}; struct Attributes const *attributes, uint8_t width, uint8_t height) {
#define NB_TILES 10 * 10
static void writePng(png_structp png, png_infop pngInfo, uint8_t width, uint8_t height, uint16_t palettes[][4], struct Attribute const *attributes, uint8_t tileData[][8][8]) {
uint8_t const nbTiles = width * height; uint8_t const nbTiles = width * height;
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop pngInfo = png_create_info_struct(png);
if (setjmp(png_jmpbuf(png))) {
fprintf(stderr, "FATAL: An error occurred while writing image \"%s\"", filename);
exit(1);
}
FILE *file = fopen(filename, "wb");
if (file == NULL) {
fprintf(stderr, "FATAL: Failed to open \"%s\": %s\n", filename, strerror(errno));
exit(1);
}
png_init_io(png, file);
png_set_IHDR(png, pngInfo, width * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA, png_set_IHDR(png, pngInfo, width * 8, height * 8, 8, PNG_COLOR_TYPE_RGB_ALPHA,
getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7, getRandomBits(1) ? PNG_INTERLACE_NONE : PNG_INTERLACE_ADAM7,
@@ -82,19 +190,24 @@ static void writePng(png_structp png, png_infop pngInfo, uint8_t width, uint8_t
// While it would be nice to write the image little by little, I really don't want to handle // While it would be nice to write the image little by little, I really don't want to handle
// interlacing myself. (We're doing interlacing to test that RGBGFX correctly handles it.) // interlacing myself. (We're doing interlacing to test that RGBGFX correctly handles it.)
uint8_t const SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component) uint8_t const SIZEOF_PIXEL = 4; // Each pixel is 4 bytes (RGBA @ 8 bits/component)
uint8_t data[height * 8 * width * 8 * SIZEOF_PIXEL]; assert(width != 0);
uint8_t *rowPtrs[height * 8]; assert(height != 0);
uint8_t *data = malloc(height * 8 * width * 8 * SIZEOF_PIXEL);
uint8_t **rowPtrs = malloc(height * 8 * sizeof(*rowPtrs));
if (data == NULL || rowPtrs == NULL) {
fatal("Out of memory");
}
for (uint8_t y = 0; y < height * 8; ++y) { for (uint8_t y = 0; y < height * 8; ++y) {
rowPtrs[y] = &data[y * width * 8 * SIZEOF_PIXEL]; rowPtrs[y] = &data[y * width * 8 * SIZEOF_PIXEL];
} }
for (uint8_t p = 0; p < nbTiles; p++) { for (uint8_t p = 0; p < nbTiles; p++) {
uint8_t tx = 8 * (p % width), ty = 8 * (p / width); uint8_t const tx = 8 * (p % width), ty = 8 * (p / width);
for (uint8_t y = 0; y < 8; y++) { for (uint8_t y = 0; y < 8; y++) {
uint8_t * const row = rowPtrs[ty + y]; uint8_t * const row = rowPtrs[ty + y];
for (uint8_t x = 0; x < 8; x++) { for (uint8_t x = 0; x < 8; x++) {
uint8_t * const pixel = &row[(tx + x) * SIZEOF_PIXEL]; uint8_t * const pixel = &row[(tx + x) * SIZEOF_PIXEL];
uint16_t color = palettes[attributes[p].palette][tileData[p][y][x]]; uint16_t const color = palettes[attributes[p].palette][tileData[p][y][x]];
pixel[0] = _5to8(color & 0x1F); pixel[0] = _5to8(color & 0x1F);
pixel[1] = _5to8(color >> 5 & 0x1F); pixel[1] = _5to8(color >> 5 & 0x1F);
pixel[2] = _5to8(color >> 10 & 0x1F); pixel[2] = _5to8(color >> 10 & 0x1F);
@@ -102,174 +215,106 @@ static void writePng(png_structp png, png_infop pngInfo, uint8_t width, uint8_t
} }
} }
} }
png_set_rows(png, pngInfo, rowPtrs); png_set_rows(png, pngInfo, rowPtrs);
png_write_png(png, pngInfo, PNG_TRANSFORM_IDENTITY, NULL); png_write_png(png, pngInfo, PNG_TRANSFORM_IDENTITY, NULL);
fclose(file);
free(rowPtrs);
free(data);
png_destroy_write_struct(&png, &pngInfo);
} }
static void generate_random_image(png_structp png, png_infop pngInfo) { static void generate_random_image(char const *filename) {
struct Attribute attributes[NB_TILES]; #define MIN_TILES_PER_SIDE 3
uint8_t tileData[NB_TILES][8][8]; #define MAX_TILES ((MIN_TILES_PER_SIDE + 7) * (MIN_TILES_PER_SIDE + 7))
// These two are in tiles, not pixels, and in range [3; 10], hence `NB_TILES` above struct Attributes attributes[MAX_TILES];
// Both width and height are 4-bit values, so nbTiles is 8-bit (OK!) unsigned char tileData[MAX_TILES][8][8];
uint8_t const width = getRandomBits(3) + 3, height = getRandomBits(3) + 3, uint8_t width = getRandomBits(3) + MIN_TILES_PER_SIDE,
nbTiles = width * height; height = getRandomBits(3) + MIN_TILES_PER_SIDE;
for (uint8_t tile = 0; tile < (width * height); tile++) {
for (uint8_t p = 0; p < nbTiles; p++) { generate_tile_attributes(attributes + tile);
uint8_t pal; // If a tile contains only one color, then there's no tile data to generate: all pixels will
do { // use color 0
pal = getRandomBits(5); if (attributes[tile].nbColors < 2) {
} while (pal == 0 || (pal > 29)); memset(tileData[tile], 0, sizeof(tileData[tile]));
attributes[p].palette = 2 * pal + getRandomBits(1);
// Population count (nb of bits set), the simple way
static uint8_t const popcount[] = {1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4};
attributes[p].nbColors = popcount[pal - 1];
// Handle single-color tiles the simple way, without trying to pull more random bits
if (attributes[p].nbColors < 2) {
memset(tileData[p], 0, sizeof(tileData[p]));
continue; continue;
} }
uint8_t index, total;
for (index = 0, total = 0; index < p; index++) { // Find tiles with the same number of colors
if (attributes[index].nbColors == attributes[p].nbColors) { unsigned index = 0, total = 0;
for (; index < tile; index++) {
if (attributes[index].nbColors == attributes[tile].nbColors) {
total++; total++;
} }
} }
// index == p at exit assert(index == tile); // This is used as a sentinel later on to indicate no tile was found
if (total) { if (total) {
// If there are such tiles, there's a random chance that this tile will replicate one of
// those tiles (potentially rotated)
index = getRandomBits(8); index = getRandomBits(8);
if (index < total) { if (index < total) {
total = index + 1; total = index + 1;
for (index = 0; total; index++) { for (index = 0; total; index++) {
if (attributes[index].nbColors == attributes[p].nbColors) { if (attributes[index].nbColors == attributes[tile].nbColors) {
total--; total--;
} }
if (!total) { }
index--; if (total == 0) {
} index--;
} }
} else { } else {
index = p; index = tile; // Restore the sentinel
} }
} }
if (index != p) {
unsigned rotation = getRandomBits(2);
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
tileData[p][y][x] =
tileData[index][y ^ ((rotation & 2) ? 7 : 0)][x ^ ((rotation & 1) ? 7 : 0)];
}
}
} else {
switch (attributes[p].nbColors) {
case 2: // Two-color tiles only need one random bit per pixel
for (uint8_t y = 0; y < 8; y++)
for (uint8_t x = 0; x < 8; x++)
tileData[p][y][x] = getRandomBits(1);
break;
case 4: // 4-color tiles can use two random bits per pixel
for (uint8_t y = 0; y < 8; y++)
for (uint8_t x = 0; x < 8; x++)
tileData[p][y][x] = getRandomBits(2);
break;
case 3: // 3-color tiles must draw two random bits, but reject them if out of range
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
do {
index = getRandomBits(2);
} while (index == 3);
tileData[p][y][x] = index;
}
}
break;
default: // 1-color tiles were handled earlier
unreachable_();
}
}
}
uint16_t colors[10]; if (index == tile) {
for (uint8_t p = 0; p < 10; p++) { generate_tile_data(tileData[tile], attributes[index].nbColors);
colors[p] = getRandomBits(15); } else {
} copy_tile_data(tileData[tile], tileData[index]);
// Randomly make color #0 of all palettes transparent }
if (!getRandomBits(2)) {
colors[0] |= 0x8000;
colors[5] |= 0x8000;
} }
uint16_t palettes[60][4]; uint16_t palettes[60][4];
for (uint8_t p = 0; p < 60; p++) { generate_palettes(palettes);
uint16_t const *subpal = &colors[p & 1 ? 5 : 0]; write_image(filename, palettes, tileData, attributes, width, height);
uint8_t total = 0;
for (uint8_t index = 0; index < 5; index++) {
if (p & (2 << index)) {
palettes[p][total++] = subpal[index];
}
}
}
writePng(png, pngInfo, width, height, palettes, attributes, tileData);
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc < 2) { if (argc < 3 || argc > 4) {
fputs("usage: randtilegen <basename> [<basename> [...]]\n", stderr); fprintf(stderr, "usage: %s <input file> <basename> [<maxcount>]\n", argv[0]);
return 2; return 2;
} }
size_t maxBasenameLen = 0; seed = fopen(argv[1], "rb");
for (int index = 1; index < argc; index++) { if (!seed) {
size_t length = strlen(argv[index]); fprintf(stderr, "FATAL: Cannot open seed file (%s)\n", strerror(errno));
if (length > maxBasenameLen) { return 1;
maxBasenameLen = length; }
size_t const nameLen = strlen(argv[2]);
unsigned long long maxcount = ULLONG_MAX;
if (argc > 3) {
char *error;
maxcount = strtoull(argv[3], &error, 0);
if (*error != '\0') {
fatal("invalid count");
} }
} }
char filename[maxBasenameLen + sizeof("65535.png")]; char *filename = malloc(nameLen + sizeof(XSTR(ULLONG_MAX) ".png"));
for (uint16_t i = 0;; i++) { // 65k images ought to be enough if (!filename) {
for (int index = 1; index < argc; index++) { fatal("out of memory");
int len = sprintf(filename, "%s%" PRIu16 ".rng", argv[index], i);
rngRecorder = fopen(filename, "wb");
if (!rngRecorder) {
perror(filename);
return 1;
}
filename[len - 3] = 'p'; // `.rng` -> `.png`
FILE *img = fopen(filename, "wb");
if (!img) {
perror(filename);
return 1;
}
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png) {
perror("png_create_write_struct");
return 1;
}
png_infop pngInfo = png_create_info_struct(png);
if (!pngInfo) {
perror("png_create_info_struct");
return 1;
}
if (setjmp(png_jmpbuf(png))) {
fprintf(stderr, "FATAL: an error occurred while writing image \"%s\"\n", filename);
return 1;
}
// Ensure that image generation starts on byte boundaries
// (This is necessary so that all involved random bits are recorded in the `.rng` file)
flushRng();
png_init_io(png, img);
generate_random_image(png, pngInfo);
png_destroy_write_struct(&png, &pngInfo);
fclose(img);
fclose(rngRecorder);
}
if (i == UINT16_MAX) {
break;
}
} }
memcpy(filename, argv[2], nameLen);
for (unsigned long long count = 0; count < maxcount; count++) {
sprintf(&filename[nameLen], "%llu.png", count);
generate_random_image(filename);
// Reset the global random state so that subsequent images don't share a random byte
randbits = 0;
randcount = 0;
}
return 0;
} }

View File

@@ -1,5 +1,18 @@
#include <sys/stat.h>
#include <sys/wait.h> // For `execProg` (Windows is its special little snowflake again)
#ifndef _MSC_VER
#include <sys/stat.h>
#include <sys/wait.h>
#include <spawn.h>
#include <unistd.h>
#else
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h` to avoid conflicts
#include <windows.h>
#include <errhandlingapi.h>
#include <processthreadsapi.h>
#undef max // This macro conflicts with `std::numeric_limits<...>::max()`
#endif
#include <algorithm> #include <algorithm>
#include <array> #include <array>
@@ -10,11 +23,10 @@
#include <limits> #include <limits>
#include <memory> #include <memory>
#include <png.h> #include <png.h>
#include <spawn.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <string>
#include <vector> #include <vector>
#include "defaultinitalloc.hpp" #include "defaultinitalloc.hpp"
@@ -267,12 +279,12 @@ public:
~Png() { png_destroy_read_struct(&png, &info, nullptr); } ~Png() { png_destroy_read_struct(&png, &info, nullptr); }
}; };
static int execProg(char const *name, char * const *argv, static char *execProg(char const *name, char * const *argv) {
posix_spawn_file_actions_t const *actions = nullptr) { #ifndef _MSC_VER
pid_t pid; pid_t pid;
int err = posix_spawn(&pid, argv[0], actions, nullptr, argv, nullptr); int err = posix_spawn(&pid, argv[0], nullptr, nullptr, argv, nullptr);
if (err != 0) { if (err != 0) {
return err; return strerror(err);
} }
siginfo_t info; siginfo_t info;
@@ -286,7 +298,74 @@ static int execProg(char const *name, char * const *argv,
fatal("%s returned with status %d", name, info.si_status); fatal("%s returned with status %d", name, info.si_status);
} }
return 0; #else /* defined(_MSC_VER) */
auto winStrerror = [](DWORD errnum) {
LPTSTR buf;
if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_MAX_WIDTH_MASK,
nullptr, errnum, 0, (LPTSTR)&buf, 0, nullptr)
== 0) {
fatal("Failed to get error message for error 0x%x", errnum);
}
return buf;
};
char cmdLine[32768]; // Max command line size on Windows
char *ptr = cmdLine;
for (size_t i = 0; argv[i]; ++i) {
char const *src = argv[i];
// I miss you, `stpcpy`
while (*src) {
*ptr++ = *src++;
}
*ptr++ = ' ';
}
*ptr = '\0';
STARTUPINFOA startupInfo;
GetStartupInfoA(&startupInfo);
STARTUPINFOA childStartupInfo{sizeof(startupInfo),
nullptr,
nullptr,
nullptr,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
nullptr,
0,
0,
0};
PROCESS_INFORMATION child;
if (CreateProcessA(nullptr, cmdLine, nullptr, nullptr, true, 0, nullptr, nullptr,
&childStartupInfo, &child)
== 0) {
return winStrerror(GetLastError());
}
DWORD status;
do {
if (GetExitCodeProcess(child.hProcess, &status) == 0) {
fatal("Error waiting for %s: %ls", name, winStrerror(GetLastError()));
}
} while (status == STILL_ACTIVE);
CloseHandle(child.hProcess);
CloseHandle(child.hThread);
if (status != 0) {
fatal("%s returned with status %ld", name, status);
}
#endif
return nullptr;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
@@ -296,20 +375,13 @@ int main(int argc, char **argv) {
} }
{ {
posix_spawn_file_actions_t action;
// Putting these directly in the array makes them const or something.
char path[] = "./randtilegen", file[] = "out"; char path[] = "./randtilegen", file[] = "out";
char *args[] = {path, file, nullptr}; char *args[] = {path, argv[1], file, nullptr};
posix_spawn_file_actions_init(&action); if (auto ret = execProg("randtilegen", args); ret != nullptr) {
posix_spawn_file_actions_addopen(&action, 0, argv[1], O_RDONLY, 0);
if (int ret = execProg("randtilegen", args, &action); ret != 0) {
fatal("Failed to excute ./randtilegen (%s). Is it in the current working directory?", fatal("Failed to excute ./randtilegen (%s). Is it in the current working directory?",
strerror(ret)); ret);
} }
posix_spawn_file_actions_destroy(&action);
} }
{ {
@@ -321,9 +393,8 @@ int main(int argc, char **argv) {
// Also copy the trailing `nullptr` // Also copy the trailing `nullptr`
std::copy_n(&argv[2], argc - 1, std::back_inserter(args)); std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
if (int ret = execProg("rgbgfx conversion", args.data()); ret != 0) { if (auto ret = execProg("rgbgfx conversion", args.data()); ret != nullptr) {
fatal("Failed to execute ../../rgbgfx (%s). Is it in the parent directory?", fatal("Failed to execute ../../rgbgfx (%s). Is it in the parent directory?", ret);
strerror(ret));
} }
} }
@@ -340,8 +411,8 @@ int main(int argc, char **argv) {
// Also copy the trailing `nullptr` // Also copy the trailing `nullptr`
std::copy_n(&argv[2], argc - 1, std::back_inserter(args)); std::copy_n(&argv[2], argc - 1, std::back_inserter(args));
if (int ret = execProg("rgbgfx reversal", args.data()); ret != 0) { if (auto ret = execProg("rgbgfx reversal", args.data()); ret != nullptr) {
fatal("Failed to execute ../../rgbgfx -r (%s)", strerror(ret)); fatal("Failed to execute ../../rgbgfx -r (%s)", ret);
} }
} }