diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/aif2pcm/Makefile | 4 | ||||
-rw-r--r-- | tools/aif2pcm/main.c | 842 | ||||
-rw-r--r-- | tools/bin2c/Makefile | 4 | ||||
-rw-r--r-- | tools/gbagfx/Makefile | 4 | ||||
-rw-r--r-- | tools/mid2agb/Makefile | 4 | ||||
-rw-r--r-- | tools/preproc/Makefile | 4 | ||||
-rw-r--r-- | tools/preproc/asm_file.cpp | 39 | ||||
-rw-r--r-- | tools/preproc/c_file.cpp | 334 | ||||
-rw-r--r-- | tools/preproc/c_file.h | 8 | ||||
-rw-r--r-- | tools/preproc/char_util.h | 4 | ||||
-rw-r--r-- | tools/preproc/preproc.cpp | 14 | ||||
-rw-r--r-- | tools/ramscrgen/Makefile | 4 | ||||
-rw-r--r-- | tools/ramscrgen/main.cpp | 26 | ||||
-rw-r--r-- | tools/ramscrgen/sym_file.cpp | 67 | ||||
-rw-r--r-- | tools/ramscrgen/sym_file.h | 3 | ||||
-rw-r--r-- | tools/rsfont/Makefile | 4 | ||||
-rw-r--r-- | tools/scaninc/Makefile | 10 | ||||
-rw-r--r-- | tools/scaninc/asm_file.cpp | 191 | ||||
-rw-r--r-- | tools/scaninc/asm_file.h | 119 | ||||
-rw-r--r-- | tools/scaninc/c_file.cpp | 287 | ||||
-rw-r--r-- | tools/scaninc/c_file.h | 53 | ||||
-rw-r--r-- | tools/scaninc/scaninc.cpp | 382 | ||||
-rw-r--r-- | tools/scaninc/scaninc.h | 59 |
23 files changed, 1728 insertions, 738 deletions
diff --git a/tools/aif2pcm/Makefile b/tools/aif2pcm/Makefile index ec6343b12..611c0e2df 100644 --- a/tools/aif2pcm/Makefile +++ b/tools/aif2pcm/Makefile @@ -1,6 +1,6 @@ CC = gcc -CFLAGS = -Wall -Wextra -Wno-switch -std=c11 -O2 +CFLAGS = -Wall -Wextra -Wno-switch -std=c11 -O2 -s LIBS = -lm @@ -9,7 +9,7 @@ SRCS = main.c extended.c .PHONY: clean aif2pcm: $(SRCS) - $(CC) $(CFLAGS) $(SRCS) -o $@ $(LIBS) + $(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS) clean: $(RM) aif2pcm aif2pcm.exe diff --git a/tools/aif2pcm/main.c b/tools/aif2pcm/main.c index dd2d368a5..fbb024a1d 100644 --- a/tools/aif2pcm/main.c +++ b/tools/aif2pcm/main.c @@ -23,6 +23,7 @@ #include <string.h> #include <stdbool.h> #include <stdint.h> +#include <limits.h> /* extended.c */ void ieee754_write_extended (double, uint8_t*); @@ -52,43 +53,94 @@ typedef struct { unsigned long num_samples; uint8_t *samples; uint8_t midi_note; + bool has_loop; unsigned long loop_offset; double sample_rate; + unsigned long real_num_samples; } AifData; +struct Bytes { + unsigned long length; + uint8_t *data; +}; -char * get_file_extension(char *filename) +struct Bytes *read_bytearray(const char *filename) { - char *dot = strrchr(filename, '.'); - if (!dot || dot == filename) + struct Bytes *bytes = malloc(sizeof(struct Bytes)); + FILE *f = fopen(filename, "rb"); + if (!f) { - FATAL_ERROR("Input file has no file extension.\n"); + FATAL_ERROR("Failed to open '%s' for reading!\n", filename); } + fseek(f, 0, SEEK_END); + bytes->length = ftell(f); + fseek(f, 0, SEEK_SET); + bytes->data = malloc(bytes->length); + unsigned long read = fread(bytes->data, bytes->length, 1, f); + fclose(f); + if (read <= 0) + { + FATAL_ERROR("Failed to read data from '%s'!\n", filename); + } + return bytes; +} - return dot + 1; +void write_bytearray(const char *filename, struct Bytes *bytes) +{ + FILE *f = fopen(filename, "wb"); + if (!f) + { + FATAL_ERROR("Failed to open '%s' for writing!\n", filename); + } + fwrite(bytes->data, bytes->length, 1, f); + fclose(f); } -void change_file_extension(char *filename, const char *new_extension) +void free_bytearray(struct Bytes *bytes) { - char *dot = strrchr(filename, '.'); - if (!dot || dot == filename) + free(bytes->data); + free(bytes); +} + +char *get_file_extension(char *filename) +{ + char *index = strrchr(filename, '.'); + if (!index || index == filename) { - FATAL_ERROR("Input file has no file extension.\n"); + return NULL; } + return index + 1; +} - memcpy(dot + 1, new_extension, 3); +char *new_file_extension(char *filename, char *ext) +{ + char *index = strrchr(filename, '.'); + if (!index || index == filename) + { + index = filename + strlen(filename); + } + int length = index - filename; + char *new_filename = malloc(length + 1 + strlen(ext) + 1); + if (new_filename) + { + strcpy(new_filename, filename); + new_filename[length] = '.'; + strcpy(new_filename + length + 1, ext); + } + return new_filename; } -AifData *read_aif(uint8_t * aif_file_data, unsigned long aif_file_data_size) +void read_aif(struct Bytes *aif, AifData *aif_data) { - AifData *aif_data = (AifData *)malloc(sizeof(AifData)); + aif_data->has_loop = false; + aif_data->num_samples = 0; unsigned long pos = 0; char chunk_name[5]; chunk_name[4] = '\0'; char chunk_type[5]; chunk_type[4] = '\0'; // Check for FORM Chunk - memcpy(chunk_name, aif_file_data + pos, 4); + memcpy(chunk_name, &aif->data[pos], 4); pos += 4; if (strcmp(chunk_name, "FORM") != 0) { @@ -96,19 +148,19 @@ AifData *read_aif(uint8_t * aif_file_data, unsigned long aif_file_data_size) } // Read size of whole file. - unsigned long whole_chunk_size = aif_file_data[pos++] << 24; - whole_chunk_size |= (aif_file_data[pos++] << 16); - whole_chunk_size |= (aif_file_data[pos++] << 8); - whole_chunk_size |= (uint8_t)aif_file_data[pos++]; + unsigned long whole_chunk_size = aif->data[pos++] << 24; + whole_chunk_size |= (aif->data[pos++] << 16); + whole_chunk_size |= (aif->data[pos++] << 8); + whole_chunk_size |= (uint8_t)aif->data[pos++]; - unsigned long expected_whole_chunk_size = aif_file_data_size - 8; + unsigned long expected_whole_chunk_size = aif->length - 8; if (whole_chunk_size != expected_whole_chunk_size) { FATAL_ERROR("FORM Chunk ckSize '%lu' doesn't match actual size '%lu'!\n", whole_chunk_size, expected_whole_chunk_size); } // Check for AIFF Form Type - memcpy(chunk_type, aif_file_data + pos, 4); + memcpy(chunk_type, &aif->data[pos], 4); pos += 4; if (strcmp(chunk_type, "AIFF") != 0) { @@ -118,44 +170,44 @@ AifData *read_aif(uint8_t * aif_file_data, unsigned long aif_file_data_size) unsigned long num_sample_frames = 0; // Read all the Chunks to populate the AifData struct. - while ((pos + 8) < aif_file_data_size) + while ((pos + 8) < aif->length) { // Read Chunk id - memcpy(chunk_name, aif_file_data + pos, 4); + memcpy(chunk_name, &aif->data[pos], 4); pos += 4; - unsigned long chunk_size = (aif_file_data[pos++] << 24); - chunk_size |= (aif_file_data[pos++] << 16); - chunk_size |= (aif_file_data[pos++] << 8); - chunk_size |= aif_file_data[pos++]; + unsigned long chunk_size = (aif->data[pos++] << 24); + chunk_size |= (aif->data[pos++] << 16); + chunk_size |= (aif->data[pos++] << 8); + chunk_size |= aif->data[pos++]; - if ((pos + chunk_size) > aif_file_data_size) + if ((pos + chunk_size) > aif->length) { FATAL_ERROR("%s chunk at 0x%lx reached end of file before finishing\n", chunk_name, pos); } if (strcmp(chunk_name, "COMM") == 0) { - short num_channels = (aif_file_data[pos++] << 8); - num_channels |= (uint8_t)aif_file_data[pos++]; + short num_channels = (aif->data[pos++] << 8); + num_channels |= (uint8_t)aif->data[pos++]; if (num_channels != 1) { FATAL_ERROR("numChannels (%d) in the COMM Chunk must be 1!\n", num_channels); } - num_sample_frames = (aif_file_data[pos++] << 24); - num_sample_frames |= (aif_file_data[pos++] << 16); - num_sample_frames |= (aif_file_data[pos++] << 8); - num_sample_frames |= (uint8_t)aif_file_data[pos++]; + num_sample_frames = (aif->data[pos++] << 24); + num_sample_frames |= (aif->data[pos++] << 16); + num_sample_frames |= (aif->data[pos++] << 8); + num_sample_frames |= (uint8_t)aif->data[pos++]; - short sample_size = (aif_file_data[pos++] << 8); - sample_size |= (uint8_t)aif_file_data[pos++]; + short sample_size = (aif->data[pos++] << 8); + sample_size |= (uint8_t)aif->data[pos++]; if (sample_size != 8) { FATAL_ERROR("sampleSize (%d) in the COMM Chunk must be 8!\n", sample_size); } - double sample_rate = ieee754_read_extended((uint8_t*)(aif_file_data + pos)); + double sample_rate = ieee754_read_extended((uint8_t*)(aif->data + pos)); pos += 10; aif_data->sample_rate = sample_rate; @@ -167,46 +219,47 @@ AifData *read_aif(uint8_t * aif_file_data, unsigned long aif_file_data_size) } else if (strcmp(chunk_name, "MARK") == 0) { - unsigned short num_markers = (aif_file_data[pos++] << 8); - num_markers |= (uint8_t)aif_file_data[pos++]; - - unsigned long loop_start = 0; + unsigned short num_markers = (aif->data[pos++] << 8); + num_markers |= (uint8_t)aif->data[pos++]; // Read each marker and look for the "START" marker. for (int i = 0; i < num_markers; i++) { - unsigned short marker_id = (aif_file_data[pos++] << 8); - marker_id |= (uint8_t)aif_file_data[pos++]; + unsigned short marker_id = (aif->data[pos++] << 8); + marker_id |= (uint8_t)aif->data[pos++]; - unsigned long marker_position = (aif_file_data[pos++] << 24); - marker_position |= (aif_file_data[pos++] << 16); - marker_position |= (aif_file_data[pos++] << 8); - marker_position |= (uint8_t)aif_file_data[pos++]; + unsigned long marker_position = (aif->data[pos++] << 24); + marker_position |= (aif->data[pos++] << 16); + marker_position |= (aif->data[pos++] << 8); + marker_position |= (uint8_t)aif->data[pos++]; // Marker id is a pascal-style string. - uint8_t marker_name_size = aif_file_data[pos++]; + uint8_t marker_name_size = aif->data[pos++]; char *marker_name = (char *)malloc((marker_name_size + 1) * sizeof(char)); - memcpy(marker_name, aif_file_data + pos, marker_name_size); + memcpy(marker_name, &aif->data[pos], marker_name_size); marker_name[marker_name_size] = '\0'; pos += marker_name_size; if (strcmp(marker_name, "START") == 0) { - loop_start = marker_position; + aif_data->loop_offset = marker_position; + aif_data->has_loop = true; } else if (strcmp(marker_name, "END") == 0) { + if (!aif_data->has_loop) { + aif_data->loop_offset = marker_position; + aif_data->has_loop = true; + } aif_data->num_samples = marker_position; } free(marker_name); } - - aif_data->loop_offset = loop_start; } else if (strcmp(chunk_name, "INST") == 0) { - uint8_t midi_note = (uint8_t)aif_file_data[pos++]; + uint8_t midi_note = (uint8_t)aif->data[pos++]; aif_data->midi_note = midi_note; @@ -218,384 +271,559 @@ AifData *read_aif(uint8_t * aif_file_data, unsigned long aif_file_data_size) // SKip offset and blockSize pos += 8; - uint8_t *sample_data = (uint8_t *)malloc(num_sample_frames * sizeof(uint8_t)); - memcpy(sample_data, aif_file_data + pos, num_sample_frames); + unsigned long num_samples = chunk_size - 8; + uint8_t *sample_data = (uint8_t *)malloc(num_samples * sizeof(uint8_t)); + memcpy(sample_data, &aif->data[pos], num_samples); aif_data->samples = sample_data; - pos += num_sample_frames; + aif_data->real_num_samples = num_samples; + pos += chunk_size - 8; } else { - // Skip over stuff we unsupported chunks. + // Skip over unsupported chunks. pos += chunk_size; } } +} - return aif_data; +// This is a table of deltas between sample values in compressed PCM data. +const int gDeltaEncodingTable[] = { + 0, 1, 4, 9, 16, 25, 36, 49, + -64, -49, -36, -25, -16, -9, -4, -1, +}; + +struct Bytes *delta_decompress(struct Bytes *delta, unsigned int expected_length) +{ + struct Bytes *pcm = malloc(sizeof(struct Bytes)); + pcm->length = expected_length; + pcm->data = malloc(pcm->length + 0x40); + + uint8_t hi, lo; + unsigned int i = 0; + unsigned int j = 0; + int k; + int8_t base; + while (i < delta->length) + { + base = (int8_t)delta->data[i++]; + pcm->data[j++] = (uint8_t)base; + if (i >= delta->length) + { + break; + } + if (j >= pcm->length) + { + break; + } + lo = delta->data[i] & 0xf; + base += gDeltaEncodingTable[lo]; + pcm->data[j++] = base; + i++; + if (i >= delta->length) + { + break; + } + if (j >= pcm->length) + { + break; + } + for (k = 0; k < 31; k++) + { + hi = (delta->data[i] >> 4) & 0xf; + base += gDeltaEncodingTable[hi]; + pcm->data[j++] = base; + if (j >= pcm->length) + { + break; + } + lo = delta->data[i] & 0xf; + base += gDeltaEncodingTable[lo]; + pcm->data[j++] = base; + i++; + if (i >= delta->length) + { + break; + } + if (j >= pcm->length) + { + break; + } + } + if (j >= pcm->length) + { + break; + } + } + + pcm->length = j; + return pcm; } -// Reads an .aif file and produces a .pcm file containing an array of 8-bit samples. -void aif2pcm(const char *aif_filename) +int get_delta_index(uint8_t sample, uint8_t prev_sample) { - // Get .pcm filename. - char *pcm_filename = malloc(strlen(aif_filename) + 1); + int best_error = INT_MAX; + int best_index = -1; - if (!pcm_filename) + for (int i = 0; i < 16; i++) { - FATAL_ERROR("Failed to allocate space for pcm filename.\n"); + uint8_t new_sample = prev_sample + gDeltaEncodingTable[i]; + int error = sample > new_sample ? sample - new_sample : new_sample - sample; + + if (error < best_error) + { + best_error = error; + best_index = i; + } } - strcpy(pcm_filename, aif_filename); - change_file_extension(pcm_filename, "pcm"); + return best_index; +} - // Get .metadata filename. - char *metadata_filename = malloc(strlen(aif_filename) + 1); +struct Bytes *delta_compress(struct Bytes *pcm) +{ + struct Bytes *delta = malloc(sizeof(struct Bytes)); + // estimate the length so we can malloc + int num_blocks = pcm->length / 64; + delta->length = num_blocks * 33; - if (!metadata_filename) + int extra = pcm->length % 64; + if (extra) + { + delta->length += 1; + extra -= 1; + } + if (extra) { - FATAL_ERROR("Failed to allocate space for metadata filename.\n"); + delta->length += 1; + extra -= 1; + } + if (extra) + { + delta->length += (extra + 1) / 2; } - strcpy(metadata_filename, aif_filename); - change_file_extension(metadata_filename, "bin"); + delta->data = malloc(delta->length + 33); - // Open the given .aif file so we can read its contents. - FILE *aif_file; - aif_file = fopen(aif_filename, "rb"); - if (!aif_file) + unsigned int i = 0; + unsigned int j = 0; + int k; + uint8_t base; + int delta_index; + + while (i < pcm->length) { - FATAL_ERROR("Failed to open '%s' for reading!\n", aif_filename); + base = pcm->data[i++]; + delta->data[j++] = base; + + if (i >= pcm->length) + { + break; + } + delta_index = get_delta_index(pcm->data[i++], base); + base += gDeltaEncodingTable[delta_index]; + delta->data[j++] = delta_index; + + for (k = 0; k < 31; k++) + { + if (i >= pcm->length) + { + break; + } + delta_index = get_delta_index(pcm->data[i++], base); + base += gDeltaEncodingTable[delta_index]; + delta->data[j] = (delta_index << 4); + + if (i >= pcm->length) + { + break; + } + delta_index = get_delta_index(pcm->data[i++], base); + base += gDeltaEncodingTable[delta_index]; + delta->data[j++] |= delta_index; + } } - // Get file length. - fseek(aif_file, 0, SEEK_END); - unsigned long aif_file_length = ftell(aif_file); - fseek(aif_file, 0, SEEK_SET); + delta->length = j; + + return delta; +} + +#define STORE_U32_LE(dest, value) \ +do { \ + *(dest) = (value) & 0xff; \ + *((dest) + 1) = ((value) >> 8) & 0xff; \ + *((dest) + 2) = ((value) >> 16) & 0xff; \ + *((dest) + 3) = ((value) >> 24) & 0xff; \ +} while (0) + +#define LOAD_U32_LE(var, src) \ +do { \ + (var) = *(src); \ + (var) |= (*((src) + 1) << 8); \ + (var) |= (*((src) + 2) << 16); \ + (var) |= (*((src) + 3) << 24); \ +} while (0) + +// Reads an .aif file and produces a .pcm file containing an array of 8-bit samples. +void aif2pcm(const char *aif_filename, const char *pcm_filename, bool compress) +{ + struct Bytes *aif = read_bytearray(aif_filename); + AifData aif_data = {0}; + read_aif(aif, &aif_data); - // Create buffer for samples. - uint8_t *aif_file_data = (uint8_t *)malloc(aif_file_length * sizeof(uint8_t)); - if (!aif_file_data) + int header_size = 0x10; + struct Bytes *pcm; + struct Bytes output = {0}; + + if (compress) { - FATAL_ERROR("Failed to allocate buffer for aif file data!\n"); + struct Bytes *input = malloc(sizeof(struct Bytes)); + input->data = aif_data.samples; + input->length = aif_data.real_num_samples; + pcm = delta_compress(input); + free(input); } - - // Populate buffer from file. - unsigned long read = fread(aif_file_data, aif_file_length, 1, aif_file); - fclose(aif_file); - if (read <= 0) + else { - FATAL_ERROR("Failed to read data from '%s'!\n", aif_filename); + pcm = malloc(sizeof(struct Bytes)); + pcm->data = aif_data.samples; + pcm->length = aif_data.real_num_samples; } - - AifData *aif_data = read_aif(aif_file_data, aif_file_length); - - // Write the output .pcm file - FILE *pcm_file; - pcm_file = fopen(pcm_filename, "wb"); - fwrite(aif_data->samples, aif_data->num_samples, 1, pcm_file); - fclose(pcm_file); - - // Write the output .bin file containing .aif metadata. - FILE *metadata_file; - metadata_file = fopen(metadata_filename, "wb"); - uint32_t pitch_adjust = (uint32_t)(aif_data->sample_rate * 1024); - fwrite(&pitch_adjust, sizeof(uint32_t), 1, metadata_file); - fwrite(&(aif_data->loop_offset), sizeof(uint32_t), 1, metadata_file); - uint32_t adjusted_num_samples = (uint32_t)(aif_data->num_samples - 1); - fwrite(&adjusted_num_samples, sizeof(uint32_t), 1, metadata_file); - fclose(metadata_file); - - free(aif_data->samples); - free(aif_data); - free(aif_file_data); - free(metadata_filename); - free(pcm_filename); + output.length = header_size + pcm->length; + output.data = malloc(output.length); + + uint32_t pitch_adjust = (uint32_t)(aif_data.sample_rate * 1024); + uint32_t loop_offset = (uint32_t)(aif_data.loop_offset); + uint32_t adjusted_num_samples = (uint32_t)(aif_data.num_samples - 1); + uint32_t flags = 0; + if (aif_data.has_loop) flags |= 0x40000000; + if (compress) flags |= 1; + STORE_U32_LE(output.data + 0, flags); + STORE_U32_LE(output.data + 4, pitch_adjust); + STORE_U32_LE(output.data + 8, loop_offset); + STORE_U32_LE(output.data + 12, adjusted_num_samples); + memcpy(&output.data[header_size], pcm->data, pcm->length); + write_bytearray(pcm_filename, &output); + + free(aif->data); + free(aif); + free(pcm); + free(output.data); + free(aif_data.samples); } // Reads a .pcm file containing an array of 8-bit samples and produces an .aif file. // See http://www-mmsp.ece.mcgill.ca/documents/audioformats/aiff/Docs/AIFF-1.3.pdf for .aif file specification. -void pcm2aif(const char *pcm_filename, char base_note, long pitch_adjust, long loop_start) +void pcm2aif(const char *pcm_filename, const char *aif_filename, uint32_t base_note) { - // Get .aif filename. - char *aif_filename = malloc(strlen(pcm_filename) + 1); + struct Bytes *pcm = read_bytearray(pcm_filename); - if (!aif_filename) - { - FATAL_ERROR("Failed to allocate space for aif filename.\n"); - } + AifData *aif_data = malloc(sizeof(AifData)); - strcpy(aif_filename, pcm_filename); - change_file_extension(aif_filename, "aif"); + uint32_t flags; + LOAD_U32_LE(flags, pcm->data + 0); + aif_data->has_loop = flags & 0x40000000; + bool compressed = flags & 1; - // Open the given .pcm file so we can read its 8-bit samples. - FILE *pcm_file; - pcm_file = fopen(pcm_filename, "rb"); - if (!pcm_file) - { - FATAL_ERROR("Failed to open '%s' for reading!\n", pcm_filename); - } + uint32_t pitch_adjust; + LOAD_U32_LE(pitch_adjust, pcm->data + 4); + aif_data->sample_rate = pitch_adjust / 1024.0; - // Get file length. - fseek(pcm_file, 0, SEEK_END); - unsigned long num_samples = ftell(pcm_file); - fseek(pcm_file, 0, SEEK_SET); + LOAD_U32_LE(aif_data->loop_offset, pcm->data + 8); + LOAD_U32_LE(aif_data->num_samples, pcm->data + 12); + aif_data->num_samples += 1; - unsigned long num_samples_extended = num_samples * 1; - - // Create buffer for samples. - signed char *pcm_samples = (signed char *)malloc(num_samples_extended * sizeof(signed char)); - if (!pcm_samples) + if (compressed) { - FATAL_ERROR("Failed to allocate buffer for pcm samples!\n"); + struct Bytes *delta = pcm; + uint8_t *pcm_data = pcm->data; + delta->length -= 0x10; + delta->data += 0x10; + pcm = delta_decompress(delta, aif_data->num_samples); + free(pcm_data); + free(delta); } - - // Populate buffer from file. - unsigned long read = fread(pcm_samples, num_samples, 1, pcm_file); - fclose(pcm_file); - if (read <= 0) + else { - FATAL_ERROR("Failed to read data from '%s'!\n", pcm_filename); + pcm->length -= 0x10; + pcm->data += 0x10; } - // Allocate buffer for output .aif file. - unsigned long aif_file_size = 54 + 60 + num_samples_extended; - char *aif_buffer = (char *)malloc(aif_file_size * sizeof(char)); - if (!aif_buffer) - { - FATAL_ERROR("Failed to allocate buffer for aif file!\n"); - } + aif_data->samples = malloc(pcm->length); + memcpy(aif_data->samples, pcm->data, pcm->length); + + struct Bytes *aif = malloc(sizeof(struct Bytes)); + aif->length = 54 + 60 + pcm->length; + aif->data = malloc(aif->length); long pos = 0; // First, write the FORM header chunk. // FORM Chunk ckID - aif_buffer[pos++] = 'F'; - aif_buffer[pos++] = 'O'; - aif_buffer[pos++] = 'R'; - aif_buffer[pos++] = 'M'; + aif->data[pos++] = 'F'; + aif->data[pos++] = 'O'; + aif->data[pos++] = 'R'; + aif->data[pos++] = 'M'; // FORM Chunk ckSize - unsigned long data_size = aif_file_size - 8; - aif_buffer[pos++] = ((data_size >> 24) & 0xFF); - aif_buffer[pos++] = ((data_size >> 16) & 0xFF); - aif_buffer[pos++] = ((data_size >> 8) & 0xFF); - aif_buffer[pos++] = (data_size & 0xFF); + unsigned long form_size = pos; + unsigned long data_size = aif->length - 8; + aif->data[pos++] = ((data_size >> 24) & 0xFF); + aif->data[pos++] = ((data_size >> 16) & 0xFF); + aif->data[pos++] = ((data_size >> 8) & 0xFF); + aif->data[pos++] = (data_size & 0xFF); // FORM Chunk formType - aif_buffer[pos++] = 'A'; - aif_buffer[pos++] = 'I'; - aif_buffer[pos++] = 'F'; - aif_buffer[pos++] = 'F'; + aif->data[pos++] = 'A'; + aif->data[pos++] = 'I'; + aif->data[pos++] = 'F'; + aif->data[pos++] = 'F'; // Next, write the Common Chunk // Common Chunk ckID - aif_buffer[pos++] = 'C'; - aif_buffer[pos++] = 'O'; - aif_buffer[pos++] = 'M'; - aif_buffer[pos++] = 'M'; + aif->data[pos++] = 'C'; + aif->data[pos++] = 'O'; + aif->data[pos++] = 'M'; + aif->data[pos++] = 'M'; // Common Chunk ckSize - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 18; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 18; // Common Chunk numChannels - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 1; // 1 channel + aif->data[pos++] = 0; + aif->data[pos++] = 1; // 1 channel // Common Chunk numSampleFrames - aif_buffer[pos++] = ((num_samples_extended >> 24) & 0xFF); - aif_buffer[pos++] = ((num_samples_extended >> 16) & 0xFF); - aif_buffer[pos++] = ((num_samples_extended >> 8) & 0xFF); - aif_buffer[pos++] = (num_samples_extended & 0xFF); + aif->data[pos++] = ((aif_data->num_samples >> 24) & 0xFF); + aif->data[pos++] = ((aif_data->num_samples >> 16) & 0xFF); + aif->data[pos++] = ((aif_data->num_samples >> 8) & 0xFF); + aif->data[pos++] = (aif_data->num_samples & 0xFF); // Common Chunk sampleSize - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 8; // 8 bits per sample + aif->data[pos++] = 0; + aif->data[pos++] = 8; // 8 bits per sample // Common Chunk sampleRate - double sample_rate = pitch_adjust / 1024.0; + //double sample_rate = pitch_adjust / 1024.0; uint8_t sample_rate_buffer[10]; - ieee754_write_extended(sample_rate, sample_rate_buffer); + ieee754_write_extended(aif_data->sample_rate, sample_rate_buffer); for (int i = 0; i < 10; i++) { - aif_buffer[pos++] = sample_rate_buffer[i]; + aif->data[pos++] = sample_rate_buffer[i]; } - // Marker Chunk ckID - aif_buffer[pos++] = 'M'; - aif_buffer[pos++] = 'A'; - aif_buffer[pos++] = 'R'; - aif_buffer[pos++] = 'K'; - - // Marker Chunk ckSize - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 24; - - // Marker Chunk numMarkers - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 2; - - // Marker loop start - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 1; // id = 1 - - aif_buffer[pos++] = ((loop_start >> 24) & 0xFF); - aif_buffer[pos++] = ((loop_start >> 16) & 0xFF); - aif_buffer[pos++] = ((loop_start >> 8) & 0xFF); - aif_buffer[pos++] = (loop_start & 0xFF); // position - - aif_buffer[pos++] = 5; // pascal-style string length - aif_buffer[pos++] = 'S'; - aif_buffer[pos++] = 'T'; - aif_buffer[pos++] = 'A'; - aif_buffer[pos++] = 'R'; - aif_buffer[pos++] = 'T'; // markerName - - // Marker loop end - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 2; // id = 2 - - long loop_end = num_samples; - aif_buffer[pos++] = ((loop_end >> 24) & 0xFF); - aif_buffer[pos++] = ((loop_end >> 16) & 0xFF); - aif_buffer[pos++] = ((loop_end >> 8) & 0xFF); - aif_buffer[pos++] = (loop_end & 0xFF); // position - - aif_buffer[pos++] = 3; // pascal-style string length - aif_buffer[pos++] = 'E'; - aif_buffer[pos++] = 'N'; - aif_buffer[pos++] = 'D'; + if (aif_data->has_loop) + { + // Marker Chunk ckID + aif->data[pos++] = 'M'; + aif->data[pos++] = 'A'; + aif->data[pos++] = 'R'; + aif->data[pos++] = 'K'; + + // Marker Chunk ckSize + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 12 + (aif_data->has_loop ? 12 : 0); + + // Marker Chunk numMarkers + aif->data[pos++] = 0; + aif->data[pos++] = (aif_data->has_loop ? 2 : 1); + + // Marker loop start + aif->data[pos++] = 0; + aif->data[pos++] = 1; // id = 1 + + long loop_start = aif_data->loop_offset; + aif->data[pos++] = ((loop_start >> 24) & 0xFF); + aif->data[pos++] = ((loop_start >> 16) & 0xFF); + aif->data[pos++] = ((loop_start >> 8) & 0xFF); + aif->data[pos++] = (loop_start & 0xFF); // position + + aif->data[pos++] = 5; // pascal-style string length + aif->data[pos++] = 'S'; + aif->data[pos++] = 'T'; + aif->data[pos++] = 'A'; + aif->data[pos++] = 'R'; + aif->data[pos++] = 'T'; // markerName + + // Marker loop end + aif->data[pos++] = 0; + aif->data[pos++] = (aif_data->has_loop ? 2 : 1); // id = 2 + + long loop_end = aif_data->num_samples; + aif->data[pos++] = ((loop_end >> 24) & 0xFF); + aif->data[pos++] = ((loop_end >> 16) & 0xFF); + aif->data[pos++] = ((loop_end >> 8) & 0xFF); + aif->data[pos++] = (loop_end & 0xFF); // position + + aif->data[pos++] = 3; // pascal-style string length + aif->data[pos++] = 'E'; + aif->data[pos++] = 'N'; + aif->data[pos++] = 'D'; + } // Instrument Chunk ckID - aif_buffer[pos++] = 'I'; - aif_buffer[pos++] = 'N'; - aif_buffer[pos++] = 'S'; - aif_buffer[pos++] = 'T'; + aif->data[pos++] = 'I'; + aif->data[pos++] = 'N'; + aif->data[pos++] = 'S'; + aif->data[pos++] = 'T'; // Instrument Chunk ckSize - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 20; - - aif_buffer[pos++] = base_note; // baseNote - aif_buffer[pos++] = 0; // detune - aif_buffer[pos++] = 0; // lowNote - aif_buffer[pos++] = 127; // highNote - aif_buffer[pos++] = 1; // lowVelocity - aif_buffer[pos++] = 127; // highVelocity - aif_buffer[pos++] = 0; // gain (hi) - aif_buffer[pos++] = 0; // gain (lo) + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 20; + + aif->data[pos++] = base_note; // baseNote + aif->data[pos++] = 0; // detune + aif->data[pos++] = 0; // lowNote + aif->data[pos++] = 127; // highNote + aif->data[pos++] = 1; // lowVelocity + aif->data[pos++] = 127; // highVelocity + aif->data[pos++] = 0; // gain (hi) + aif->data[pos++] = 0; // gain (lo) // Instrument Chunk sustainLoop - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 1; // playMode = ForwardLooping + aif->data[pos++] = 0; + aif->data[pos++] = 1; // playMode = ForwardLooping - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 1; // beginLoop marker id + aif->data[pos++] = 0; + aif->data[pos++] = 1; // beginLoop marker id - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 2; // endLoop marker id + aif->data[pos++] = 0; + aif->data[pos++] = 2; // endLoop marker id // Instrument Chunk releaseLoop - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 1; // playMode = ForwardLooping + aif->data[pos++] = 0; + aif->data[pos++] = 1; // playMode = ForwardLooping - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 1; // beginLoop marker id + aif->data[pos++] = 0; + aif->data[pos++] = 1; // beginLoop marker id - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 2; // endLoop marker id + aif->data[pos++] = 0; + aif->data[pos++] = 2; // endLoop marker id // Finally, write the Sound Data Chunk // Sound Data Chunk ckID - aif_buffer[pos++] = 'S'; - aif_buffer[pos++] = 'S'; - aif_buffer[pos++] = 'N'; - aif_buffer[pos++] = 'D'; + aif->data[pos++] = 'S'; + aif->data[pos++] = 'S'; + aif->data[pos++] = 'N'; + aif->data[pos++] = 'D'; // Sound Data Chunk ckSize - unsigned long sound_data_size = num_samples_extended + 8; - aif_buffer[pos++] = ((sound_data_size >> 24) & 0xFF); - aif_buffer[pos++] = ((sound_data_size >> 16) & 0xFF); - aif_buffer[pos++] = ((sound_data_size >> 8) & 0xFF); - aif_buffer[pos++] = (sound_data_size & 0xFF); + unsigned long sound_data_size = pcm->length + 8; + aif->data[pos++] = ((sound_data_size >> 24) & 0xFF); + aif->data[pos++] = ((sound_data_size >> 16) & 0xFF); + aif->data[pos++] = ((sound_data_size >> 8) & 0xFF); + aif->data[pos++] = (sound_data_size & 0xFF); // Sound Data Chunk offset - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; // Sound Data Chunk blockSize - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; - aif_buffer[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; + aif->data[pos++] = 0; // Sound Data Chunk soundData - for (int i = 0; i < loop_start; i++) + for (unsigned int i = 0; i < aif_data->loop_offset; i++) { - aif_buffer[pos++] = pcm_samples[i]; + aif->data[pos++] = aif_data->samples[i]; } int j = 0; - for (unsigned int i = loop_start; i < num_samples_extended; i++) + for (unsigned int i = aif_data->loop_offset; i < pcm->length; i++) { - int pcm_index = loop_start + (j++ % (num_samples - loop_start - 1)); - aif_buffer[pos++] = pcm_samples[pcm_index]; + int pcm_index = aif_data->loop_offset + (j++ % (pcm->length - aif_data->loop_offset)); + aif->data[pos++] = aif_data->samples[pcm_index]; } - // Write the .aif file contents. - FILE *aif_file; - aif_file = fopen(aif_filename, "wb"); - if (!aif_file) - { - FATAL_ERROR("Failed to open '%s' for writing!\n", aif_filename); - } + aif->length = pos; + + // Go back and rewrite ckSize + data_size = aif->length - 8; + aif->data[form_size + 0] = ((data_size >> 24) & 0xFF); + aif->data[form_size + 1] = ((data_size >> 16) & 0xFF); + aif->data[form_size + 2] = ((data_size >> 8) & 0xFF); + aif->data[form_size + 3] = (data_size & 0xFF); - fwrite(aif_buffer, 1, aif_file_size, aif_file); - fclose(aif_file); + write_bytearray(aif_filename, aif); - free(aif_buffer); - free(pcm_samples); - free(aif_filename); + free(aif->data); + free(aif); +} + +void usage(void) +{ + fprintf(stderr, "Usage: aif2pcm bin_file [aif_file]\n"); + fprintf(stderr, " aif2pcm aif_file [bin_file] [--compress]\n"); } int main(int argc, char **argv) { if (argc < 2) { - FATAL_ERROR("Usage: aif2pcm <aif_file>\n"); + usage(); + exit(1); } - char *input_filename = argv[1]; - char *extension = get_file_extension(input_filename); + char *input_file = argv[1]; + char *extension = get_file_extension(input_file); + char *output_file; + bool compressed = false; + + if (argc > 3) + { + for (int i = 3; i < argc; i++) + { + if (strcmp(argv[i], "--compress") == 0) + { + compressed = true; + } + } + } - if (strcmp(extension, "aif") == 0) + if (strcmp(extension, "aif") == 0 || strcmp(extension, "aiff") == 0) { - aif2pcm(input_filename); + if (argc >= 3) + { + output_file = argv[2]; + aif2pcm(input_file, output_file, compressed); + } + else + { + output_file = new_file_extension(input_file, "bin"); + aif2pcm(input_file, output_file, compressed); + free(output_file); + } } - else if (strcmp(extension, "pcm") == 0) + else if (strcmp(extension, "bin") == 0) { - if (argc < 5) + if (argc >= 3) { - FATAL_ERROR("Usage: aif2pcm <pcm_file> <midi_note> <pitch_adjust> <loop_start>\n"); + output_file = argv[2]; + pcm2aif(input_file, output_file, 60); + } + else + { + output_file = new_file_extension(input_file, "aif"); + pcm2aif(input_file, output_file, 60); + free(output_file); } - - char base_note = atoi(argv[2]); - long pitch_adjust = atol(argv[3]); - long loop_start = atol(argv[4]); - pcm2aif(input_filename, base_note, pitch_adjust, loop_start); } else { - FATAL_ERROR("Input file must be .aif or .pcm: '%s'\n", input_filename); + FATAL_ERROR("Input file must be .aif or .bin: '%s'\n", input_file); } return 0; diff --git a/tools/bin2c/Makefile b/tools/bin2c/Makefile index eee19af22..bd5f60490 100644 --- a/tools/bin2c/Makefile +++ b/tools/bin2c/Makefile @@ -1,13 +1,13 @@ CC = gcc -CFLAGS = -Wall -Wextra -std=c11 -O2 +CFLAGS = -Wall -Wextra -std=c11 -O2 -s .PHONY: clean SRCS = bin2c.c bin2c: $(SRCS) - $(CC) $(CFLAGS) $(SRCS) -o $@ + $(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) clean: $(RM) bin2c bin2c.exe diff --git a/tools/gbagfx/Makefile b/tools/gbagfx/Makefile index de4ea5c42..9a5dee1cc 100644 --- a/tools/gbagfx/Makefile +++ b/tools/gbagfx/Makefile @@ -1,6 +1,6 @@ CC = gcc -CFLAGS = -Wall -Wextra -std=c11 -O2 -DPNG_SKIP_SETJMP_CHECK +CFLAGS = -Wall -Wextra -std=c11 -O2 -s -DPNG_SKIP_SETJMP_CHECK LIBS = -lpng -lz @@ -9,7 +9,7 @@ SRCS = main.c convert_png.c gfx.c jasc_pal.c lz.c rl.c util.c font.c .PHONY: clean gbagfx: $(SRCS) convert_png.h gfx.h global.h jasc_pal.h lz.h rl.h util.h font.h - $(CC) $(CFLAGS) $(SRCS) -o $@ $(LIBS) + $(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS) clean: $(RM) gbagfx gbagfx.exe diff --git a/tools/mid2agb/Makefile b/tools/mid2agb/Makefile index 3215169d3..accd81882 100644 --- a/tools/mid2agb/Makefile +++ b/tools/mid2agb/Makefile @@ -1,6 +1,6 @@ CXX := g++ -CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch +CXXFLAGS := -std=c++11 -O2 -s -Wall -Wno-switch SRCS := agb.cpp error.cpp main.cpp midi.cpp tables.cpp @@ -9,7 +9,7 @@ HEADERS := agb.h error.h main.h midi.h tables.h .PHONY: clean mid2agb: $(SRCS) $(HEADERS) - $(CXX) $(CXXFLAGS) $(SRCS) -o $@ + $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS) clean: $(RM) mid2agb mid2agb.exe diff --git a/tools/preproc/Makefile b/tools/preproc/Makefile index 2d577c79f..1f4c58e21 100644 --- a/tools/preproc/Makefile +++ b/tools/preproc/Makefile @@ -1,6 +1,6 @@ CXX := g++ -CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch +CXXFLAGS := -std=c++11 -O2 -s -Wall -Wno-switch SRCS := asm_file.cpp c_file.cpp charmap.cpp preproc.cpp string_parser.cpp \ utf8.cpp @@ -11,7 +11,7 @@ HEADERS := asm_file.h c_file.h char_util.h charmap.h preproc.h string_parser.h \ .PHONY: clean preproc: $(SRCS) $(HEADERS) - $(CXX) $(CXXFLAGS) $(SRCS) -o $@ + $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS) clean: $(RM) preproc preproc.exe diff --git a/tools/preproc/asm_file.cpp b/tools/preproc/asm_file.cpp index 7deaccc8a..bb296b78b 100644 --- a/tools/preproc/asm_file.cpp +++ b/tools/preproc/asm_file.cpp @@ -111,45 +111,22 @@ void AsmFile::RemoveComments() m_buffer[pos++] = ' '; m_buffer[pos++] = ' '; - char commentStringChar = 0; - for (;;) { if (m_buffer[pos] == 0) return; - if (commentStringChar != 0) + if (m_buffer[pos] == '*' && m_buffer[pos + 1] == '/') { - if (m_buffer[pos] == '\\' && m_buffer[pos + 1] == commentStringChar) - { - m_buffer[pos++] = ' '; - m_buffer[pos++] = ' '; - } - else - { - if (m_buffer[pos] == commentStringChar) - commentStringChar = 0; - if (m_buffer[pos] != '\n') - m_buffer[pos] = ' '; - pos++; - } + m_buffer[pos++] = ' '; + m_buffer[pos++] = ' '; + break; } else { - if (m_buffer[pos] == '*' && m_buffer[pos + 1] == '/') - { - m_buffer[pos++] = ' '; - m_buffer[pos++] = ' '; - break; - } - else - { - if (m_buffer[pos] == '"' || m_buffer[pos] == '\'') - commentStringChar = m_buffer[pos]; - if (m_buffer[pos] != '\n') - m_buffer[pos] = ' '; - pos++; - } + if (m_buffer[pos] != '\n') + m_buffer[pos] = ' '; + pos++; } } } @@ -517,7 +494,7 @@ bool AsmFile::IsAtEnd() // Output the current location to set gas's logical file and line numbers. void AsmFile::OutputLocation() { - printf("# %ld \"%s\"\n", m_lineNum, m_filename.c_str()); + std::printf("# %ld \"%s\"\n", m_lineNum, m_filename.c_str()); } // Reports a diagnostic message. diff --git a/tools/preproc/c_file.cpp b/tools/preproc/c_file.cpp index aed53d44b..5bfdee086 100644 --- a/tools/preproc/c_file.cpp +++ b/tools/preproc/c_file.cpp @@ -20,6 +20,8 @@ #include <cstdio> #include <cstdarg> +#include <string> +#include <memory> #include "preproc.h" #include "c_file.h" #include "char_util.h" @@ -72,8 +74,6 @@ CFile::~CFile() void CFile::Preproc() { - bool inConcatMode = false; - bool noTerminator = false; char stringChar = 0; while (m_pos < m_size) @@ -94,85 +94,297 @@ void CFile::Preproc() } else { + if (m_buffer[m_pos] == '\n') + m_lineNum++; std::putchar(m_buffer[m_pos]); m_pos++; } } else { - if (inConcatMode ? m_buffer[m_pos] == '"' - : (m_buffer[m_pos] == '_' || m_buffer[m_pos] == '@') && m_buffer[m_pos + 1] == '"') + TryConvertString(); + TryConvertIncbin(); + + if (m_pos >= m_size) + break; + + char c = m_buffer[m_pos++]; + + std::putchar(c); + + if (c == '\n') + m_lineNum++; + else if (c == '"') + stringChar = '"'; + else if (c == '\'') + stringChar = '\''; + } + } +} + +bool CFile::ConsumeHorizontalWhitespace() +{ + if (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ') + { + m_pos++; + return true; + } + + return false; +} + +bool CFile::ConsumeNewline() +{ + if (m_buffer[m_pos] == '\r' && m_buffer[m_pos + 1] == '\n') + { + m_pos += 2; + m_lineNum++; + return true; + } + + if (m_buffer[m_pos] == '\n') + { + m_pos++; + m_lineNum++; + return true; + } + + return false; +} + +void CFile::SkipWhitespace() +{ + while (ConsumeHorizontalWhitespace() || ConsumeNewline()) + ; +} + +void CFile::TryConvertString() +{ + long oldPos = m_pos; + long oldLineNum = m_lineNum; + bool noTerminator = false; + + if (m_buffer[m_pos] != '_' || (m_pos > 0 && IsIdentifierChar(m_buffer[m_pos - 1]))) + return; + + m_pos++; + + if (m_buffer[m_pos] == '_') + { + noTerminator = true; + m_pos++; + } + + SkipWhitespace(); + + if (m_buffer[m_pos] != '(') + { + m_pos = oldPos; + m_lineNum = oldLineNum; + return; + } + + m_pos++; + + SkipWhitespace(); + + std::printf("{ "); + + while (1) + { + SkipWhitespace(); + + if (m_buffer[m_pos] == '"') + { + unsigned char s[kMaxStringLength]; + int length; + StringParser stringParser(m_buffer, m_size); + + try { - if (!inConcatMode) - { - noTerminator = (m_buffer[m_pos] == '@'); - m_pos++; // skip past underscore or at-sign - } - - unsigned char s[kMaxStringLength]; - int length; - StringParser stringParser(m_buffer, m_size); - - try - { - m_pos += stringParser.ParseString(m_pos, s, length); - } - catch (std::runtime_error e) - { - RaiseError(e.what()); - } - - if (!inConcatMode) - { - std::printf("{ "); - } - - inConcatMode = true; - - for (int i = 0; i < length; i++) - printf("0x%02X, ", s[i]); + m_pos += stringParser.ParseString(m_pos, s, length); } - else + catch (std::runtime_error e) { - char c = m_buffer[m_pos++]; + RaiseError(e.what()); + } + + for (int i = 0; i < length; i++) + printf("0x%02X, ", s[i]); + } + else if (m_buffer[m_pos] == ')') + { + m_pos++; + break; + } + else + { + if (m_pos >= m_size) + RaiseError("unexpected EOF"); + if (IsAsciiPrintable(m_buffer[m_pos])) + RaiseError("unexpected character '%c'", m_buffer[m_pos]); + else + RaiseError("unexpected character '\\x%02X'", m_buffer[m_pos]); + } + } + + if (noTerminator) + std::printf(" }"); + else + std::printf("0xFF }"); +} + +bool CFile::CheckIdentifier(const std::string& ident) +{ + unsigned int i; - if (c == '\r') - { - if (m_buffer[m_pos] == '\n') - { - m_pos++; - } + for (i = 0; i < ident.length() && m_pos + i < (unsigned)m_size; i++) + if (ident[i] != m_buffer[m_pos + i]) + return false; - c = '\n'; - } + return (i == ident.length()); +} - if ((c != ' ' && c != '\t' && c != '\n') && inConcatMode) - { - if (noTerminator) - std::printf(" }"); - else - std::printf("0xFF }"); +std::unique_ptr<unsigned char[]> CFile::ReadWholeFile(const std::string& path, int& size) +{ + FILE* fp = std::fopen(path.c_str(), "rb"); - inConcatMode = false; - } + if (fp == nullptr) + RaiseError("Failed to open \"%s\" for reading.\n", path.c_str()); - std::putchar(c); + std::fseek(fp, 0, SEEK_END); - if (c == '\n') - m_lineNum++; - else if (c == '"') - stringChar = '"'; - else if (m_buffer[m_pos] == '\'') - stringChar = '\''; - } + size = std::ftell(fp); + + std::unique_ptr<unsigned char[]> buffer = std::unique_ptr<unsigned char[]>(new unsigned char[size]); + + std::rewind(fp); + + if (std::fread(buffer.get(), size, 1, fp) != 1) + RaiseError("Failed to read \"%s\".\n", path.c_str()); + + std::fclose(fp); + + return buffer; +} + +int ExtractData(const std::unique_ptr<unsigned char[]>& buffer, int offset, int size) +{ + switch (size) + { + case 1: + return buffer[offset]; + case 2: + return (buffer[offset + 1] << 8) + | buffer[offset]; + case 4: + return (buffer[offset + 3] << 24) + | (buffer[offset + 2] << 16) + | (buffer[offset + 1] << 8) + | buffer[offset]; + default: + FATAL_ERROR("Invalid size passed to ExtractData.\n"); + } +} + +void CFile::TryConvertIncbin() +{ + std::string idents[6] = { "INCBIN_S8", "INCBIN_U8", "INCBIN_S16", "INCBIN_U16", "INCBIN_S32", "INCBIN_U32" }; + int incbinType = -1; + + for (int i = 0; i < 6; i++) + { + if (CheckIdentifier(idents[i])) + { + incbinType = i; + break; } } - if (inConcatMode) + if (incbinType == -1) + return; + + int size = 1 << (incbinType / 2); + bool isSigned = ((incbinType % 2) == 0); + + long oldPos = m_pos; + long oldLineNum = m_lineNum; + + m_pos += idents[incbinType].length(); + + SkipWhitespace(); + + if (m_buffer[m_pos] != '(') { - printf("0xFF }"); - RaiseWarning("string at end of file"); + m_pos = oldPos; + m_lineNum = oldLineNum; + return; } + + m_pos++; + + SkipWhitespace(); + + if (m_buffer[m_pos] != '"') + RaiseError("expected double quote"); + + m_pos++; + + int startPos = m_pos; + + while (m_buffer[m_pos] != '"') + { + if (m_buffer[m_pos] == 0) + { + if (m_pos >= m_size) + RaiseError("unexpected EOF in path string"); + else + RaiseError("unexpected null character in path string"); + } + + if (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n') + RaiseError("unexpected end of line character in path string"); + + if (m_buffer[m_pos] == '\\') + RaiseError("unexpected escape in path string"); + + m_pos++; + } + + std::string path(&m_buffer[startPos], m_pos - startPos); + + m_pos++; + + SkipWhitespace(); + + if (m_buffer[m_pos] != ')') + RaiseError("expected ')'"); + + m_pos++; + + std::printf("{"); + + int fileSize; + std::unique_ptr<unsigned char[]> buffer = ReadWholeFile(path, fileSize); + + if ((fileSize % size) != 0) + RaiseError("Size %d doesn't evenly divide file size %d.\n", size, fileSize); + + int count = fileSize / size; + int offset = 0; + + for (int i = 0; i < count; i++) + { + int data = ExtractData(buffer, offset, size); + offset += size; + + if (isSigned) + std::printf("%d,", data); + else + std::printf("%uu,", data); + } + + std::printf("}"); } // Reports a diagnostic message. diff --git a/tools/preproc/c_file.h b/tools/preproc/c_file.h index b6041cc00..7369aba85 100644 --- a/tools/preproc/c_file.h +++ b/tools/preproc/c_file.h @@ -24,6 +24,7 @@ #include <cstdarg> #include <cstdint> #include <string> +#include <memory> #include "preproc.h" class CFile @@ -42,6 +43,13 @@ private: long m_lineNum; std::string m_filename; + bool ConsumeHorizontalWhitespace(); + bool ConsumeNewline(); + void SkipWhitespace(); + void TryConvertString(); + std::unique_ptr<unsigned char[]> ReadWholeFile(const std::string& path, int& size); + bool CheckIdentifier(const std::string& ident); + void TryConvertIncbin(); void ReportDiagnostic(const char* type, const char* format, std::va_list args); void RaiseError(const char* format, ...); void RaiseWarning(const char* format, ...); diff --git a/tools/preproc/char_util.h b/tools/preproc/char_util.h index ab20dbc53..02a6e1ceb 100644 --- a/tools/preproc/char_util.h +++ b/tools/preproc/char_util.h @@ -56,13 +56,13 @@ inline bool IsAsciiPrintable(unsigned char c) return (c >= ' ' && c <= '~'); } -// Returns whether the character can start the identifier of a "{FOO}" constant in strings. +// Returns whether the character can start a C identifier or the identifier of a "{FOO}" constant in strings. inline bool IsIdentifierStartingChar(unsigned char c) { return IsAsciiAlpha(c) || c == '_'; } -// Returns whether the character can be used in the identifier of a "{FOO}" constant in strings. +// Returns whether the character can be used in a C identifier or the identifier of a "{FOO}" constant in strings. inline bool IsIdentifierChar(unsigned char c) { return IsAsciiAlphanum(c) || c == '_'; diff --git a/tools/preproc/preproc.cpp b/tools/preproc/preproc.cpp index b51861580..8320a2d27 100644 --- a/tools/preproc/preproc.cpp +++ b/tools/preproc/preproc.cpp @@ -31,15 +31,15 @@ void PrintAsmBytes(unsigned char *s, int length) { if (length > 0) { - printf("\t.byte "); + std::printf("\t.byte "); for (int i = 0; i < length; i++) { - printf("0x%02X", s[i]); + std::printf("0x%02X", s[i]); if (i < length - 1) - printf(", "); + std::printf(", "); } - putchar('\n'); + std::putchar('\n'); } } @@ -89,8 +89,8 @@ void PreprocAsmFile(std::string filename) if (globalLabel.length() != 0) { - printf("\t.global %s\n", globalLabel.c_str()); - printf("%s:\n", globalLabel.c_str()); + std::printf("\t.global %s\n", globalLabel.c_str()); + std::printf("%s:\n", globalLabel.c_str()); } else { @@ -134,7 +134,7 @@ int main(int argc, char **argv) { if (argc != 3) { - fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE", argv[0]); + std::fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE", argv[0]); return 1; } diff --git a/tools/ramscrgen/Makefile b/tools/ramscrgen/Makefile index e18ae99f2..6c4ca28b6 100644 --- a/tools/ramscrgen/Makefile +++ b/tools/ramscrgen/Makefile @@ -1,6 +1,6 @@ CXX := g++ -CXXFLAGS := -std=c++11 -O2 -Wall -Wno-switch +CXXFLAGS := -std=c++11 -O2 -s -Wall -Wno-switch SRCS := main.cpp sym_file.cpp elf.cpp @@ -9,7 +9,7 @@ HEADERS := ramscrgen.h sym_file.h elf.h char_util.h .PHONY: clean ramscrgen: $(SRCS) $(HEADERS) - $(CXX) $(CXXFLAGS) $(SRCS) -o $@ + $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS) clean: $(RM) ramscrgen ramscrgen.exe diff --git a/tools/ramscrgen/main.cpp b/tools/ramscrgen/main.cpp index 5c803c31f..6c4f4bbd7 100644 --- a/tools/ramscrgen/main.cpp +++ b/tools/ramscrgen/main.cpp @@ -25,7 +25,7 @@ #include "sym_file.h" #include "elf.h" -void HandleCommonInclude(std::string filename, std::string sourcePath, std::string symOrderPath) +void HandleCommonInclude(std::string filename, std::string sourcePath, std::string symOrderPath, std::string lang) { auto commonSymbols = GetCommonSymbols(sourcePath + "/" + filename); @@ -40,6 +40,8 @@ void HandleCommonInclude(std::string filename, std::string sourcePath, std::stri while (!symFile.IsAtEnd()) { + symFile.HandleLangConditional(lang); + std::string label = symFile.GetLabel(false); if (label.length() == 0) @@ -71,12 +73,14 @@ void HandleCommonInclude(std::string filename, std::string sourcePath, std::stri } } -void ConvertSymFile(std::string filename, std::string sectionName, bool common, std::string sourcePath, std::string commonSymPath) +void ConvertSymFile(std::string filename, std::string sectionName, std::string lang, bool common, std::string sourcePath, std::string commonSymPath) { SymFile symFile(filename); while (!symFile.IsAtEnd()) { + symFile.HandleLangConditional(lang); + Directive directive = symFile.GetDirective(); switch (directive) @@ -87,10 +91,9 @@ void ConvertSymFile(std::string filename, std::string sectionName, bool common, symFile.ExpectEmptyRestOfLine(); printf(". = ALIGN(4);\n"); if (common) - HandleCommonInclude(incFilename, sourcePath, commonSymPath); + HandleCommonInclude(incFilename, sourcePath, commonSymPath, lang); else printf("%s(%s);\n", incFilename.c_str(), sectionName.c_str()); - printf(". = ALIGN(4);\n"); break; } case Directive::Space: @@ -133,28 +136,29 @@ void ConvertSymFile(std::string filename, std::string sectionName, bool common, int main(int argc, char **argv) { - if (argc < 3) + if (argc < 4) { - fprintf(stderr, "Usage: %s SECTION_NAME SYM_FILE [-c SRC_PATH,COMMON_SYM_PATH]", argv[0]); + fprintf(stderr, "Usage: %s SECTION_NAME SYM_FILE LANG [-c SRC_PATH,COMMON_SYM_PATH]", argv[0]); return 1; } bool common = false; std::string sectionName = std::string(argv[1]); std::string symFileName = std::string(argv[2]); + std::string lang = std::string(argv[3]); std::string sourcePath; std::string commonSymPath; - if (argc > 3) + if (argc > 4) { - if (std::strcmp(argv[3], "-c") != 0) + if (std::strcmp(argv[4], "-c") != 0) FATAL_ERROR("error: unrecognized argument \"%s\"\n", argv[4]); - if (argc < 5) + if (argc < 6) FATAL_ERROR("error: missing SRC_PATH,COMMON_SYM_PATH after \"-c\"\n"); common = true; - std::string paths = std::string(argv[4]); + std::string paths = std::string(argv[5]); std::size_t commaPos = paths.find(','); if (commaPos == std::string::npos) @@ -164,6 +168,6 @@ int main(int argc, char **argv) commonSymPath = paths.substr(commaPos + 1); } - ConvertSymFile(symFileName, sectionName, common, sourcePath, commonSymPath); + ConvertSymFile(symFileName, sectionName, lang, common, sourcePath, commonSymPath); return 0; } diff --git a/tools/ramscrgen/sym_file.cpp b/tools/ramscrgen/sym_file.cpp index 9d9e4a064..5379bd93f 100644 --- a/tools/ramscrgen/sym_file.cpp +++ b/tools/ramscrgen/sym_file.cpp @@ -53,6 +53,7 @@ SymFile::SymFile(std::string filename) : m_filename(filename) m_pos = 0; m_lineNum = 1; m_lineStart = 0; + m_inLangConditional = false; RemoveComments(); } @@ -387,12 +388,78 @@ void SymFile::ExpectEmptyRestOfLine() } } + +void SymFile::SkipLine() +{ + while (m_buffer[m_pos] != 0 && m_buffer[m_pos] != '\n') + m_pos++; + + if (m_buffer[m_pos] == '\n') + m_pos++; +} + // Checks if we're at the end of the file. bool SymFile::IsAtEnd() { return (m_pos >= m_size); } +void SymFile::HandleLangConditional(std::string lang) +{ + if (m_buffer[m_pos] != '#') + return; + + m_pos++; + + if (CheckForDirective("begin")) + { + if (m_inLangConditional) + RaiseError("already inside language conditional"); + + SkipWhitespace(); + + std::string label = GetLabel(false); + + if (label.length() == 0) + RaiseError("no language name after #begin"); + + ExpectEmptyRestOfLine(); + + if (lang == label) + { + m_inLangConditional = true; + } + else + { + while (!IsAtEnd() && m_buffer[m_pos] != '#') + SkipLine(); + + if (m_buffer[m_pos] != '#') + RaiseError("unterminated language conditional"); + + m_pos++; + + if (!CheckForDirective("end")) + RaiseError("expected #end"); + + ExpectEmptyRestOfLine(); + } + } + else if (CheckForDirective("end")) + { + if (!m_inLangConditional) + RaiseError("not inside language conditional"); + + m_inLangConditional = false; + + ExpectEmptyRestOfLine(); + } + else + { + RaiseError("unknown # directive"); + } +} + // Reports a diagnostic message. void SymFile::ReportDiagnostic(const char* type, const char* format, std::va_list args) { diff --git a/tools/ramscrgen/sym_file.h b/tools/ramscrgen/sym_file.h index 5b3cedb3b..bb0c8038d 100644 --- a/tools/ramscrgen/sym_file.h +++ b/tools/ramscrgen/sym_file.h @@ -46,7 +46,9 @@ public: std::string ReadPath(); bool ReadInteger(unsigned long& value); void ExpectEmptyRestOfLine(); + void SkipLine(); bool IsAtEnd(); + void HandleLangConditional(std::string lang); void RaiseError(const char* format, ...); void RaiseWarning(const char* format, ...); @@ -57,6 +59,7 @@ private: long m_lineNum; long m_lineStart; std::string m_filename; + bool m_inLangConditional; bool ConsumeComma(); void RemoveComments(); diff --git a/tools/rsfont/Makefile b/tools/rsfont/Makefile index d2a88c46d..544954bfc 100644 --- a/tools/rsfont/Makefile +++ b/tools/rsfont/Makefile @@ -1,6 +1,6 @@ CC = gcc -CFLAGS = -Wall -Wextra -std=c11 -O2 -DPNG_SKIP_SETJMP_CHECK +CFLAGS = -Wall -Wextra -std=c11 -O2 -s -DPNG_SKIP_SETJMP_CHECK LIBS = -lpng -lz @@ -9,7 +9,7 @@ SRCS = main.c convert_png.c util.c font.c .PHONY: clean rsfont: $(SRCS) convert_png.h gfx.h global.h util.h font.h - $(CC) $(CFLAGS) $(SRCS) -o $@ $(LIBS) + $(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS) clean: $(RM) rsfont rsfont.exe diff --git a/tools/scaninc/Makefile b/tools/scaninc/Makefile index 0efe283f1..d33dee6d2 100644 --- a/tools/scaninc/Makefile +++ b/tools/scaninc/Makefile @@ -1,13 +1,15 @@ CXX = g++ -CXXFLAGS = -Wall -std=c++11 -O2 +CXXFLAGS = -Wall -std=c++11 -O2 -s -SRCS = scaninc.cpp +SRCS = scaninc.cpp c_file.cpp asm_file.cpp + +HEADERS := scaninc.h asm_file.h c_file.h .PHONY: clean -scaninc: $(SRCS) - $(CXX) $(CXXFLAGS) $(SRCS) -o $@ +scaninc: $(SRCS) $(HEADERS) + $(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS) clean: $(RM) scaninc scaninc.exe diff --git a/tools/scaninc/asm_file.cpp b/tools/scaninc/asm_file.cpp new file mode 100644 index 000000000..c3d140bb1 --- /dev/null +++ b/tools/scaninc/asm_file.cpp @@ -0,0 +1,191 @@ +// Copyright(c) 2015-2017 YamaArashi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include <cstdio> +#include <string> +#include "scaninc.h" +#include "asm_file.h" + +AsmFile::AsmFile(std::string path) +{ + m_path = path; + + FILE *fp = std::fopen(path.c_str(), "rb"); + + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for reading.\n", path.c_str()); + + std::fseek(fp, 0, SEEK_END); + + m_size = std::ftell(fp); + + m_buffer = new char[m_size]; + + std::rewind(fp); + + if (std::fread(m_buffer, m_size, 1, fp) != 1) + FATAL_ERROR("Failed to read \"%s\".\n", path.c_str()); + + std::fclose(fp); + + m_pos = 0; + m_lineNum = 1; +} + +AsmFile::~AsmFile() +{ + delete[] m_buffer; +} + +IncDirectiveType AsmFile::ReadUntilIncDirective(std::string &path) +{ + // At the beginning of each loop iteration, the current file position + // should be at the start of a line or at the end of the file. + for (;;) + { + SkipTabsAndSpaces(); + + IncDirectiveType incDirectiveType = IncDirectiveType::None; + + if (PeekChar() == '.') + { + m_pos++; + + if (MatchIncDirective("incbin", path)) + incDirectiveType = IncDirectiveType::Incbin; + else if (MatchIncDirective("include", path)) + incDirectiveType = IncDirectiveType::Include; + } + + for (;;) + { + int c = GetChar(); + + if (c == -1) + return incDirectiveType; + + if (c == ';') + { + SkipEndOfLineComment(); + break; + } + else if (c == '/' && PeekChar() == '*') + { + m_pos++; + SkipMultiLineComment(); + } + else if (c == '"') + { + SkipString(); + } + else if (c == '\n') + { + break; + } + } + + if (incDirectiveType != IncDirectiveType::None) + return incDirectiveType; + } +} + +std::string AsmFile::ReadPath() +{ + int length = 0; + int startPos = m_pos; + + for (;;) + { + int c = GetChar(); + + if (c == '"') + break; + + if (c == -1) + FATAL_INPUT_ERROR("unexpected EOF in include string\n"); + + if (c == 0) + FATAL_INPUT_ERROR("unexpected NUL character in include string\n"); + + if (c == '\n') + FATAL_INPUT_ERROR("unexpected end of line character in include string\n"); + + // Don't bother allowing any escape sequences. + if (c == '\\') + FATAL_INPUT_ERROR("unexpected escape in include string\n"); + + length++; + + if (length > SCANINC_MAX_PATH) + FATAL_INPUT_ERROR("path is too long"); + } + + return std::string(m_buffer, startPos, length); +} + +void AsmFile::SkipEndOfLineComment() +{ + int c; + + do + { + c = GetChar(); + } while (c != -1 && c != '\n'); +} + +void AsmFile::SkipMultiLineComment() +{ + for (;;) + { + int c = GetChar(); + + if (c == '*') + { + if (PeekChar() == '/') + { + m_pos++; + return; + } + } + else if (c == -1) + { + return; + } + } +} + +void AsmFile::SkipString() +{ + for (;;) + { + int c = GetChar(); + + if (c == '"') + break; + + if (c == -1) + FATAL_INPUT_ERROR("unexpected EOF in string\n"); + + if (c == '\\') + { + c = GetChar(); + } + } +} diff --git a/tools/scaninc/asm_file.h b/tools/scaninc/asm_file.h new file mode 100644 index 000000000..ad99b757e --- /dev/null +++ b/tools/scaninc/asm_file.h @@ -0,0 +1,119 @@ +// Copyright(c) 2015-2017 YamaArashi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef ASM_FILE_H +#define ASM_FILE_H + +#include <string> +#include "scaninc.h" + +enum class IncDirectiveType +{ + None, + Include, + Incbin +}; + +class AsmFile +{ +public: + AsmFile(std::string path); + ~AsmFile(); + IncDirectiveType ReadUntilIncDirective(std::string& path); + +private: + char *m_buffer; + int m_pos; + int m_size; + int m_lineNum; + std::string m_path; + + int GetChar() + { + if (m_pos >= m_size) + return -1; + + int c = m_buffer[m_pos++]; + + if (c == '\r') + { + if (m_pos < m_size && m_buffer[m_pos++] == '\n') + { + m_lineNum++; + return '\n'; + } + else + { + FATAL_INPUT_ERROR("CR line endings are not supported\n"); + } + } + + if (c == '\n') + m_lineNum++; + + return c; + } + + // No newline translation because it's not needed for any use of this function. + int PeekChar() + { + if (m_pos >= m_size) + return -1; + + return m_buffer[m_pos]; + } + + void SkipTabsAndSpaces() + { + while (m_pos < m_size && (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')) + m_pos++; + } + + bool MatchIncDirective(std::string directiveName, std::string& path) + { + int length = directiveName.length(); + int i; + + for (i = 0; i < length && m_pos + i < m_size; i++) + if (directiveName[i] != m_buffer[m_pos + i]) + return false; + + if (i < length) + return false; + + m_pos += length; + + SkipTabsAndSpaces(); + + if (GetChar() != '"') + FATAL_INPUT_ERROR("no path after \".%s\" directive\n", directiveName.c_str()); + + path = ReadPath(); + + return true; + } + + std::string ReadPath(); + void SkipEndOfLineComment(); + void SkipMultiLineComment(); + void SkipString(); +}; + +#endif // ASM_FILE_H diff --git a/tools/scaninc/c_file.cpp b/tools/scaninc/c_file.cpp new file mode 100644 index 000000000..b82276dd6 --- /dev/null +++ b/tools/scaninc/c_file.cpp @@ -0,0 +1,287 @@ +// Copyright(c) 2017 YamaArashi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "c_file.h" + +CFile::CFile(std::string path) +{ + m_path = path; + + FILE *fp = std::fopen(path.c_str(), "rb"); + + if (fp == NULL) + FATAL_ERROR("Failed to open \"%s\" for reading.\n", path.c_str()); + + std::fseek(fp, 0, SEEK_END); + + m_size = std::ftell(fp); + + m_buffer = new char[m_size + 1]; + m_buffer[m_size] = 0; + + std::rewind(fp); + + if (std::fread(m_buffer, m_size, 1, fp) != 1) + FATAL_ERROR("Failed to read \"%s\".\n", path.c_str()); + + std::fclose(fp); + + m_pos = 0; + m_lineNum = 1; + + RemoveComments(); +} + +CFile::~CFile() +{ + delete[] m_buffer; +} + +// Removes comments to simplify further processing. +// It stops upon encountering a null character, +// which may or may not be the end of file marker. +// If it's not, the error will be caught later. +void CFile::RemoveComments() +{ + long pos = 0; + char stringChar = 0; + + for (;;) + { + if (m_buffer[pos] == 0) + return; + + if (stringChar != 0) + { + if (m_buffer[pos] == '\\' && m_buffer[pos + 1] == stringChar) + { + pos += 2; + } + else + { + if (m_buffer[pos] == stringChar) + stringChar = 0; + pos++; + } + } + else if (m_buffer[pos] == '/' && m_buffer[pos + 1] == '/') + { + while (m_buffer[pos] != '\n' && m_buffer[pos] != 0) + m_buffer[pos++] = ' '; + } + else if (m_buffer[pos] == '/' && m_buffer[pos + 1] == '*') + { + m_buffer[pos++] = ' '; + m_buffer[pos++] = ' '; + + for (;;) + { + if (m_buffer[pos] == 0) + return; + + if (m_buffer[pos] == '*' && m_buffer[pos + 1] == '/') + { + m_buffer[pos++] = ' '; + m_buffer[pos++] = ' '; + break; + } + else + { + if (m_buffer[pos] != '\n') + m_buffer[pos] = ' '; + pos++; + } + } + } + else + { + if (m_buffer[pos] == '"' || m_buffer[pos] == '\'') + stringChar = m_buffer[pos]; + pos++; + } + } +} + +void CFile::FindIncbins() +{ + char stringChar = 0; + + while (m_pos < m_size) + { + if (stringChar) + { + if (m_buffer[m_pos] == stringChar) + { + m_pos++; + stringChar = 0; + } + else if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == stringChar) + { + m_pos += 2; + } + else + { + if (m_buffer[m_pos] == '\n') + m_lineNum++; + m_pos++; + } + } + else + { + CheckIncbin(); + + if (m_pos >= m_size) + break; + + char c = m_buffer[m_pos++]; + + if (c == '\n') + m_lineNum++; + else if (c == '"') + stringChar = '"'; + else if (c == '\'') + stringChar = '\''; + else if (c == 0) + FATAL_INPUT_ERROR("unexpected null character"); + } + } +} + +bool CFile::ConsumeHorizontalWhitespace() +{ + if (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ') + { + m_pos++; + return true; + } + + return false; +} + +bool CFile::ConsumeNewline() +{ + if (m_buffer[m_pos] == '\r' && m_buffer[m_pos + 1] == '\n') + { + m_pos += 2; + m_lineNum++; + return true; + } + + if (m_buffer[m_pos] == '\n') + { + m_pos++; + m_lineNum++; + return true; + } + + return false; +} + +void CFile::SkipWhitespace() +{ + while (ConsumeHorizontalWhitespace() || ConsumeNewline()) + ; +} + +bool CFile::CheckIdentifier(const std::string& ident) +{ + unsigned int i; + + for (i = 0; i < ident.length() && m_pos + i < (unsigned)m_size; i++) + if (ident[i] != m_buffer[m_pos + i]) + return false; + + return (i == ident.length()); +} + +void CFile::CheckIncbin() +{ + std::string idents[6] = { "INCBIN_S8", "INCBIN_U8", "INCBIN_S16", "INCBIN_U16", "INCBIN_S32", "INCBIN_U32" }; + int incbinType = -1; + + for (int i = 0; i < 6; i++) + { + if (CheckIdentifier(idents[i])) + { + incbinType = i; + break; + } + } + + if (incbinType == -1) + return; + + long oldPos = m_pos; + long oldLineNum = m_lineNum; + + m_pos += idents[incbinType].length(); + + SkipWhitespace(); + + if (m_buffer[m_pos] != '(') + { + m_pos = oldPos; + m_lineNum = oldLineNum; + return; + } + + m_pos++; + + SkipWhitespace(); + + if (m_buffer[m_pos] != '"') + FATAL_INPUT_ERROR("expected double quote"); + + m_pos++; + + int startPos = m_pos; + + while (m_buffer[m_pos] != '"') + { + if (m_buffer[m_pos] == 0) + { + if (m_pos >= m_size) + FATAL_INPUT_ERROR("unexpected EOF in path string"); + else + FATAL_INPUT_ERROR("unexpected null character in path string"); + } + + if (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n') + FATAL_INPUT_ERROR("unexpected end of line character in path string"); + + if (m_buffer[m_pos] == '\\') + FATAL_INPUT_ERROR("unexpected escape in path string"); + + m_pos++; + } + + std::string path(&m_buffer[startPos], m_pos - startPos); + + m_pos++; + + SkipWhitespace(); + + if (m_buffer[m_pos] != ')') + FATAL_INPUT_ERROR("expected ')'"); + + m_pos++; + + m_incbins.emplace(path); +} diff --git a/tools/scaninc/c_file.h b/tools/scaninc/c_file.h new file mode 100644 index 000000000..922cb4639 --- /dev/null +++ b/tools/scaninc/c_file.h @@ -0,0 +1,53 @@ +// Copyright(c) 2017 YamaArashi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef C_FILE_H +#define C_FILE_H + +#include <string> +#include <set> +#include <memory> +#include "scaninc.h" + +class CFile +{ +public: + CFile(std::string path); + ~CFile(); + void FindIncbins(); + const std::set<std::string>& GetIncbins() { return m_incbins; } + +private: + char *m_buffer; + int m_pos; + int m_size; + int m_lineNum; + std::string m_path; + std::set<std::string> m_incbins; + + void RemoveComments(); + bool ConsumeHorizontalWhitespace(); + bool ConsumeNewline(); + void SkipWhitespace(); + bool CheckIdentifier(const std::string& ident); + void CheckIncbin(); +}; + +#endif // C_FILE_H diff --git a/tools/scaninc/scaninc.cpp b/tools/scaninc/scaninc.cpp index 3bd6b81a5..b6f7ba767 100644 --- a/tools/scaninc/scaninc.cpp +++ b/tools/scaninc/scaninc.cpp @@ -1,317 +1,97 @@ -// Copyright (c) 2015 YamaArashi +// Copyright(c) 2015-2017 YamaArashi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. #include <cstdio> #include <cstdlib> #include <stack> #include <set> #include <string> - -#ifdef _MSC_VER - -#define FATAL_INPUT_ERROR(format, ...) \ -do { \ - fprintf(stderr, "%s:%d " format, m_path.c_str(), m_lineNum, __VA_ARGS__); \ - exit(1); \ -} while (0) - -#define FATAL_ERROR(format, ...) \ -do { \ - fprintf(stderr, format, __VA_ARGS__); \ - exit(1); \ -} while (0) - -#else - -#define FATAL_INPUT_ERROR(format, ...) \ -do { \ - fprintf(stderr, "%s:%d " format, m_path.c_str(), m_lineNum, ##__VA_ARGS__); \ - exit(1); \ -} while (0) - -#define FATAL_ERROR(format, ...) \ -do { \ - fprintf(stderr, format, ##__VA_ARGS__); \ - exit(1); \ -} while (0) - -#endif // _MSC_VER - -#define SCANINC_MAX_PATH 255 - -enum class IncDirectiveType { - None, - Include, - Incbin -}; - -class AsmFile -{ -public: - AsmFile(std::string path); - ~AsmFile(); - IncDirectiveType ReadUntilIncDirective(std::string &path); - -private: - char *m_buffer; - int m_pos; - int m_size; - int m_lineNum; - std::string m_path; - - int GetChar() - { - if (m_pos >= m_size) - return -1; - - int c = m_buffer[m_pos++]; - - if (c == '\r') { - if (m_pos < m_size && m_buffer[m_pos++] == '\n') { - m_lineNum++; - return '\n'; - } else { - FATAL_INPUT_ERROR("CR line endings are not supported\n"); - } - } - - if (c == '\n') - m_lineNum++; - - return c; - } - - // No newline translation because it's not needed for any use of this function. - int PeekChar() - { - if (m_pos >= m_size) - return -1; - - return m_buffer[m_pos]; - } - - void SkipTabsAndSpaces() - { - while (m_pos < m_size && (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')) - m_pos++; - } - - bool MatchIncDirective(std::string directiveName, std::string &path) - { - int length = directiveName.length(); - int i; - - for (i = 0; i < length && m_pos + i < m_size; i++) - if (directiveName[i] != m_buffer[m_pos + i]) - return false; - - if (i < length) - return false; - - m_pos += length; - - SkipTabsAndSpaces(); - - if (GetChar() != '"') - FATAL_INPUT_ERROR("no path after \".%s\" directive\n", directiveName.c_str()); - - path = ReadPath(); - - return true; - } - - std::string ReadPath(); - void SkipEndOfLineComment(); - void SkipMultiLineComment(); - void SkipString(); -}; - -AsmFile::AsmFile(std::string path) -{ - m_path = path; - - FILE *fp = fopen(path.c_str(), "rb"); - - if (fp == NULL) - FATAL_ERROR("Failed to open \"%s\" for reading.\n", path.c_str()); - - fseek(fp, 0, SEEK_END); - - m_size = ftell(fp); - - m_buffer = new char[m_size]; - - rewind(fp); - - if (fread(m_buffer, m_size, 1, fp) != 1) - FATAL_ERROR("Failed to read \"%s\".\n", path.c_str()); - - fclose(fp); - - m_pos = 0; - m_lineNum = 1; -} - -AsmFile::~AsmFile() -{ - delete[] m_buffer; -} - -IncDirectiveType AsmFile::ReadUntilIncDirective(std::string &path) -{ - // At the beginning of each loop iteration, the current file position - // should be at the start of a line or at the end of the file. - for (;;) { - SkipTabsAndSpaces(); - - IncDirectiveType incDirectiveType = IncDirectiveType::None; - - if (PeekChar() == '.') { - m_pos++; - - if (MatchIncDirective("incbin", path)) - incDirectiveType = IncDirectiveType::Incbin; - else if (MatchIncDirective("include", path)) - incDirectiveType = IncDirectiveType::Include; - } - - for (;;) { - int c = GetChar(); - - if (c == -1) - return incDirectiveType; - - if (c == ';') { - SkipEndOfLineComment(); - break; - } else if (c == '/' && PeekChar() == '*') { - m_pos++; - SkipMultiLineComment(); - } else if (c == '"') { - SkipString(); - } else if (c == '\n') { - break; - } - } - - if (incDirectiveType != IncDirectiveType::None) - return incDirectiveType; - } -} - -std::string AsmFile::ReadPath() -{ - int length = 0; - int startPos = m_pos; - - for (;;) { - int c = GetChar(); - - if (c == '"') - break; - - if (c == -1) - FATAL_INPUT_ERROR("unexpected EOF in include string\n"); - - if (c == 0) - FATAL_INPUT_ERROR("unexpected NUL character in include string\n"); - - if (c == '\n') - FATAL_INPUT_ERROR("unexpected end of line character in include string\n"); - - // Don't bother allowing any escape sequences. - if (c == '\\') - FATAL_INPUT_ERROR("unexpected escape '\\%c' in include string\n", c); - - length++; - - if (length > SCANINC_MAX_PATH) - FATAL_INPUT_ERROR("path is too long"); - } - - return std::string(m_buffer, startPos, length); -} - -void AsmFile::SkipEndOfLineComment() -{ - int c; - - do { - c = GetChar(); - } while (c != -1 && c != '\n'); -} - -void AsmFile::SkipMultiLineComment() -{ - for (;;) { - int c = GetChar(); - - if (c == '*') { - if (PeekChar() == '/') { - m_pos++; - return; - } - } else if (c == -1) { - return; - } - } -} - -void AsmFile::SkipString() -{ - for (;;) { - int c = GetChar(); - - if (c == '"') - break; - - if (c == -1) - FATAL_INPUT_ERROR("unexpected EOF in string\n"); - - if (c == '\\') { - c = GetChar(); - } - } -} +#include "scaninc.h" +#include "asm_file.h" +#include "c_file.h" bool CanOpenFile(std::string path) { - FILE *fp = fopen(path.c_str(), "rb"); + FILE *fp = std::fopen(path.c_str(), "rb"); - if (fp == NULL) - return false; + if (fp == NULL) + return false; - fclose(fp); - return true; + std::fclose(fp); + return true; } int main(int argc, char **argv) { - if (argc < 2) - FATAL_ERROR("Usage: scaninc ASM_FILE_PATH\n"); - - std::stack<std::string> filesToProcess; - std::set<std::string> dependencies; - - filesToProcess.push(std::string(argv[1])); - - while (!filesToProcess.empty()) { - AsmFile file(filesToProcess.top()); - - filesToProcess.pop(); - - IncDirectiveType incDirectiveType; - std::string path; - - while ((incDirectiveType = file.ReadUntilIncDirective(path)) != IncDirectiveType::None) { - bool inserted = dependencies.insert(path).second; - if (inserted - && incDirectiveType == IncDirectiveType::Include - && CanOpenFile(path)) - filesToProcess.push(path); - } - } - - for (const std::string &path : dependencies) { - printf("%s\n", path.c_str()); - } + if (argc < 2) + FATAL_ERROR("Usage: scaninc FILE_PATH\n"); + + std::stack<std::string> filesToProcess; + std::set<std::string> dependencies; + + std::string initialPath(argv[1]); + + std::size_t pos = initialPath.find_last_of('.'); + + if (pos == std::string::npos) + FATAL_ERROR("no file extension in path \"%s\"\n", initialPath.c_str()); + + std::string extension = initialPath.substr(pos + 1); + + if (extension == "c") + { + CFile file(initialPath); + + file.FindIncbins(); + dependencies = file.GetIncbins(); + } + else if (extension == "s") + { + filesToProcess.push(std::string(argv[1])); + + while (!filesToProcess.empty()) + { + AsmFile file(filesToProcess.top()); + + filesToProcess.pop(); + + IncDirectiveType incDirectiveType; + std::string path; + + while ((incDirectiveType = file.ReadUntilIncDirective(path)) != IncDirectiveType::None) + { + bool inserted = dependencies.insert(path).second; + if (inserted + && incDirectiveType == IncDirectiveType::Include + && CanOpenFile(path)) + filesToProcess.push(path); + } + } + } + else + { + FATAL_ERROR("unknown extension \"%s\"\n", extension.c_str()); + } + + for (const std::string &path : dependencies) + { + std::printf("%s\n", path.c_str()); + } } diff --git a/tools/scaninc/scaninc.h b/tools/scaninc/scaninc.h new file mode 100644 index 000000000..30cc9611c --- /dev/null +++ b/tools/scaninc/scaninc.h @@ -0,0 +1,59 @@ +// Copyright(c) 2015-2017 YamaArashi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef SCANINC_H +#define SCANINC_H + +#include <cstdio> +#include <cstdlib> + +#ifdef _MSC_VER + +#define FATAL_INPUT_ERROR(format, ...) \ +do { \ + fprintf(stderr, "%s:%d " format, m_path.c_str(), m_lineNum, __VA_ARGS__); \ + exit(1); \ +} while (0) + +#define FATAL_ERROR(format, ...) \ +do { \ + fprintf(stderr, format, __VA_ARGS__); \ + exit(1); \ +} while (0) + +#else + +#define FATAL_INPUT_ERROR(format, ...) \ +do { \ + fprintf(stderr, "%s:%d " format, m_path.c_str(), m_lineNum, ##__VA_ARGS__); \ + exit(1); \ +} while (0) + +#define FATAL_ERROR(format, ...) \ +do { \ + fprintf(stderr, format, ##__VA_ARGS__); \ + exit(1); \ +} while (0) + +#endif // _MSC_VER + +#define SCANINC_MAX_PATH 255 + +#endif // SCANINC_H |