mirror of
https://github.com/Ninjdai1/pokeemerald.git
synced 2024-11-16 11:37:40 +01:00
adding tools from pokeruby
This commit is contained in:
parent
eeaa59d837
commit
e649e3d248
2
tools/aif2pcm/.gitignore
vendored
Normal file
2
tools/aif2pcm/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
aif2pcm
|
||||
|
20
tools/aif2pcm/LICENSE
Normal file
20
tools/aif2pcm/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2016 huderlem
|
||||
Copyright (c) 2005, 2006 by Marco Trillo <marcotrillo@gmail.com>
|
||||
|
||||
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.
|
15
tools/aif2pcm/Makefile
Normal file
15
tools/aif2pcm/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS = -Wall -Wextra -Wno-switch -Werror -std=c11 -O2 -s
|
||||
|
||||
LIBS = -lm
|
||||
|
||||
SRCS = main.c extended.c
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
aif2pcm: $(SRCS)
|
||||
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
$(RM) aif2pcm aif2pcm.exe
|
172
tools/aif2pcm/extended.c
Normal file
172
tools/aif2pcm/extended.c
Normal file
@ -0,0 +1,172 @@
|
||||
/* $Id: extended.c,v 1.8 2006/12/23 11:17:49 toad32767 Exp $ */
|
||||
/*-
|
||||
* Copyright (c) 2005, 2006 by Marco Trillo <marcotrillo@gmail.com>
|
||||
*
|
||||
* 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 <math.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Infinite & NAN values
|
||||
* for non-IEEE systems
|
||||
*/
|
||||
#ifndef HUGE_VAL
|
||||
#ifdef HUGE
|
||||
#define INFINITE_VALUE HUGE
|
||||
#define NAN_VALUE HUGE
|
||||
#endif
|
||||
#else
|
||||
#define INFINITE_VALUE HUGE_VAL
|
||||
#define NAN_VALUE HUGE_VAL
|
||||
#endif
|
||||
|
||||
/*
|
||||
* IEEE 754 Extended Precision
|
||||
*
|
||||
* Implementation here is the 80-bit extended precision
|
||||
* format of Motorola 68881, Motorola 68882 and Motorola
|
||||
* 68040 FPUs, as well as Intel 80x87 FPUs.
|
||||
*
|
||||
* See:
|
||||
* http://www.freescale.com/files/32bit/doc/fact_sheet/BR509.pdf
|
||||
*/
|
||||
/*
|
||||
* Exponent range: [-16383,16383]
|
||||
* Precision for mantissa: 64 bits with no hidden bit
|
||||
* Bias: 16383
|
||||
*/
|
||||
|
||||
/*
|
||||
* Write IEEE Extended Precision Numbers
|
||||
*/
|
||||
void
|
||||
ieee754_write_extended(double in, uint8_t* out)
|
||||
{
|
||||
int sgn, exp, shift;
|
||||
double fraction, t;
|
||||
unsigned int lexp, hexp;
|
||||
unsigned long low, high;
|
||||
|
||||
if (in == 0.0) {
|
||||
memset(out, 0, 10);
|
||||
return;
|
||||
}
|
||||
if (in < 0.0) {
|
||||
in = fabs(in);
|
||||
sgn = 1;
|
||||
} else
|
||||
sgn = 0;
|
||||
|
||||
fraction = frexp(in, &exp);
|
||||
|
||||
if (exp == 0 || exp > 16384) {
|
||||
if (exp > 16384) /* infinite value */
|
||||
low = high = 0;
|
||||
else {
|
||||
low = 0x80000000;
|
||||
high = 0;
|
||||
}
|
||||
exp = 32767;
|
||||
goto done;
|
||||
}
|
||||
fraction = ldexp(fraction, 32);
|
||||
t = floor(fraction);
|
||||
low = (unsigned long) t;
|
||||
fraction -= t;
|
||||
t = floor(ldexp(fraction, 32));
|
||||
high = (unsigned long) t;
|
||||
|
||||
/* Convert exponents < -16382 to -16382 (then they will be
|
||||
* stored as -16383) */
|
||||
if (exp < -16382) {
|
||||
shift = 0 - exp - 16382;
|
||||
high >>= shift;
|
||||
high |= (low << (32 - shift));
|
||||
low >>= shift;
|
||||
exp = -16382;
|
||||
}
|
||||
exp += 16383 - 1; /* bias */
|
||||
|
||||
done:
|
||||
lexp = ((unsigned int) exp) >> 8;
|
||||
hexp = ((unsigned int) exp) & 0xFF;
|
||||
|
||||
/* big endian */
|
||||
out[0] = ((uint8_t) sgn) << 7;
|
||||
out[0] |= (uint8_t) lexp;
|
||||
out[1] = (uint8_t) hexp;
|
||||
out[2] = (uint8_t) (low >> 24);
|
||||
out[3] = (uint8_t) ((low >> 16) & 0xFF);
|
||||
out[4] = (uint8_t) ((low >> 8) & 0xFF);
|
||||
out[5] = (uint8_t) (low & 0xFF);
|
||||
out[6] = (uint8_t) (high >> 24);
|
||||
out[7] = (uint8_t) ((high >> 16) & 0xFF);
|
||||
out[8] = (uint8_t) ((high >> 8) & 0xFF);
|
||||
out[9] = (uint8_t) (high & 0xFF);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Read IEEE Extended Precision Numbers
|
||||
*/
|
||||
double
|
||||
ieee754_read_extended(uint8_t* in)
|
||||
{
|
||||
int sgn, exp;
|
||||
unsigned long low, high;
|
||||
double out;
|
||||
|
||||
/* Extract the components from the big endian buffer */
|
||||
sgn = (int) (in[0] >> 7);
|
||||
exp = ((int) (in[0] & 0x7F) << 8) | ((int) in[1]);
|
||||
low = (((unsigned long) in[2]) << 24)
|
||||
| (((unsigned long) in[3]) << 16)
|
||||
| (((unsigned long) in[4]) << 8) | (unsigned long) in[5];
|
||||
high = (((unsigned long) in[6]) << 24)
|
||||
| (((unsigned long) in[7]) << 16)
|
||||
| (((unsigned long) in[8]) << 8) | (unsigned long) in[9];
|
||||
|
||||
if (exp == 0 && low == 0 && high == 0)
|
||||
return (sgn ? -0.0 : 0.0);
|
||||
|
||||
switch (exp) {
|
||||
case 32767:
|
||||
if (low == 0 && high == 0)
|
||||
return (sgn ? -INFINITE_VALUE : INFINITE_VALUE);
|
||||
else
|
||||
return (sgn ? -NAN_VALUE : NAN_VALUE);
|
||||
default:
|
||||
exp -= 16383; /* unbias exponent */
|
||||
|
||||
}
|
||||
|
||||
out = ldexp((double) low, -31 + exp);
|
||||
out += ldexp((double) high, -63 + exp);
|
||||
|
||||
return (sgn ? -out : out);
|
||||
}
|
830
tools/aif2pcm/main.c
Normal file
830
tools/aif2pcm/main.c
Normal file
@ -0,0 +1,830 @@
|
||||
// Copyright(c) 2016 huderlem
|
||||
//
|
||||
// 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* extended.c */
|
||||
void ieee754_write_extended (double, uint8_t*);
|
||||
double ieee754_read_extended (uint8_t*);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
fprintf(stderr, format, __VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct Bytes *read_bytearray(const char *filename)
|
||||
{
|
||||
struct Bytes *bytes = malloc(sizeof(struct Bytes));
|
||||
FILE *f = fopen(filename, "rb");
|
||||
if (!f)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 free_bytearray(struct Bytes *bytes)
|
||||
{
|
||||
free(bytes->data);
|
||||
free(bytes);
|
||||
}
|
||||
|
||||
char *get_file_extension(char *filename)
|
||||
{
|
||||
char *index = strrchr(filename, '.');
|
||||
if (!index || index == filename)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void read_aif(struct Bytes *aif, AifData *aif_data)
|
||||
{
|
||||
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->data[pos], 4);
|
||||
pos += 4;
|
||||
if (strcmp(chunk_name, "FORM") != 0)
|
||||
{
|
||||
FATAL_ERROR("Input .aif file has invalid header Chunk '%s'!\n", chunk_name);
|
||||
}
|
||||
|
||||
// Read size of whole file.
|
||||
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->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->data[pos], 4);
|
||||
pos += 4;
|
||||
if (strcmp(chunk_type, "AIFF") != 0)
|
||||
{
|
||||
FATAL_ERROR("FORM Type is '%s', but it must be AIFF!", chunk_type);
|
||||
}
|
||||
|
||||
unsigned long num_sample_frames = 0;
|
||||
|
||||
// Read all the Chunks to populate the AifData struct.
|
||||
while ((pos + 8) < aif->length)
|
||||
{
|
||||
// Read Chunk id
|
||||
memcpy(chunk_name, &aif->data[pos], 4);
|
||||
pos += 4;
|
||||
|
||||
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->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->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->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->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->data + pos));
|
||||
pos += 10;
|
||||
|
||||
aif_data->sample_rate = sample_rate;
|
||||
|
||||
if (aif_data->num_samples == 0)
|
||||
{
|
||||
aif_data->num_samples = num_sample_frames;
|
||||
}
|
||||
}
|
||||
else if (strcmp(chunk_name, "MARK") == 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->data[pos++] << 8);
|
||||
marker_id |= (uint8_t)aif->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->data[pos++];
|
||||
char *marker_name = (char *)malloc((marker_name_size + 1) * sizeof(char));
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
else if (strcmp(chunk_name, "INST") == 0)
|
||||
{
|
||||
uint8_t midi_note = (uint8_t)aif->data[pos++];
|
||||
|
||||
aif_data->midi_note = midi_note;
|
||||
|
||||
// Skip over data we don't need.
|
||||
pos += 19;
|
||||
}
|
||||
else if (strcmp(chunk_name, "SSND") == 0)
|
||||
{
|
||||
// SKip offset and blockSize
|
||||
pos += 8;
|
||||
|
||||
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;
|
||||
aif_data->real_num_samples = num_samples;
|
||||
pos += chunk_size - 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip over unsupported chunks.
|
||||
pos += chunk_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
int get_delta_index(uint8_t sample, uint8_t prev_sample)
|
||||
{
|
||||
int best_error = INT_MAX;
|
||||
int best_index = -1;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return best_index;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
int extra = pcm->length % 64;
|
||||
if (extra)
|
||||
{
|
||||
delta->length += 1;
|
||||
extra -= 1;
|
||||
}
|
||||
if (extra)
|
||||
{
|
||||
delta->length += 1;
|
||||
extra -= 1;
|
||||
}
|
||||
if (extra)
|
||||
{
|
||||
delta->length += (extra + 1) / 2;
|
||||
}
|
||||
|
||||
delta->data = malloc(delta->length + 33);
|
||||
|
||||
unsigned int i = 0;
|
||||
unsigned int j = 0;
|
||||
int k;
|
||||
uint8_t base;
|
||||
int delta_index;
|
||||
|
||||
while (i < pcm->length)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
int header_size = 0x10;
|
||||
struct Bytes *pcm;
|
||||
struct Bytes output = {0};
|
||||
|
||||
if (compress)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
pcm = malloc(sizeof(struct Bytes));
|
||||
pcm->data = aif_data.samples;
|
||||
pcm->length = aif_data.real_num_samples;
|
||||
}
|
||||
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, const char *aif_filename, uint32_t base_note)
|
||||
{
|
||||
struct Bytes *pcm = read_bytearray(pcm_filename);
|
||||
|
||||
AifData *aif_data = malloc(sizeof(AifData));
|
||||
|
||||
uint32_t flags;
|
||||
LOAD_U32_LE(flags, pcm->data + 0);
|
||||
aif_data->has_loop = flags & 0x40000000;
|
||||
bool compressed = flags & 1;
|
||||
|
||||
uint32_t pitch_adjust;
|
||||
LOAD_U32_LE(pitch_adjust, pcm->data + 4);
|
||||
aif_data->sample_rate = pitch_adjust / 1024.0;
|
||||
|
||||
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;
|
||||
|
||||
if (compressed)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
pcm->length -= 0x10;
|
||||
pcm->data += 0x10;
|
||||
}
|
||||
|
||||
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->data[pos++] = 'F';
|
||||
aif->data[pos++] = 'O';
|
||||
aif->data[pos++] = 'R';
|
||||
aif->data[pos++] = 'M';
|
||||
|
||||
// FORM Chunk ckSize
|
||||
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->data[pos++] = 'A';
|
||||
aif->data[pos++] = 'I';
|
||||
aif->data[pos++] = 'F';
|
||||
aif->data[pos++] = 'F';
|
||||
|
||||
// Next, write the Common Chunk
|
||||
// Common Chunk ckID
|
||||
aif->data[pos++] = 'C';
|
||||
aif->data[pos++] = 'O';
|
||||
aif->data[pos++] = 'M';
|
||||
aif->data[pos++] = 'M';
|
||||
|
||||
// Common Chunk ckSize
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 18;
|
||||
|
||||
// Common Chunk numChannels
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 1; // 1 channel
|
||||
|
||||
// Common Chunk numSampleFrames
|
||||
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->data[pos++] = 0;
|
||||
aif->data[pos++] = 8; // 8 bits per sample
|
||||
|
||||
// Common Chunk sampleRate
|
||||
//double sample_rate = pitch_adjust / 1024.0;
|
||||
uint8_t sample_rate_buffer[10];
|
||||
ieee754_write_extended(aif_data->sample_rate, sample_rate_buffer);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
aif->data[pos++] = sample_rate_buffer[i];
|
||||
}
|
||||
|
||||
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->data[pos++] = 'I';
|
||||
aif->data[pos++] = 'N';
|
||||
aif->data[pos++] = 'S';
|
||||
aif->data[pos++] = 'T';
|
||||
|
||||
// Instrument Chunk ckSize
|
||||
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->data[pos++] = 0;
|
||||
aif->data[pos++] = 1; // playMode = ForwardLooping
|
||||
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 1; // beginLoop marker id
|
||||
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 2; // endLoop marker id
|
||||
|
||||
// Instrument Chunk releaseLoop
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 1; // playMode = ForwardLooping
|
||||
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 1; // beginLoop marker id
|
||||
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 2; // endLoop marker id
|
||||
|
||||
// Finally, write the Sound Data Chunk
|
||||
// Sound Data Chunk ckID
|
||||
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 = 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->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
|
||||
// Sound Data Chunk blockSize
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
aif->data[pos++] = 0;
|
||||
|
||||
// Sound Data Chunk soundData
|
||||
for (unsigned int i = 0; i < aif_data->loop_offset; i++)
|
||||
{
|
||||
aif->data[pos++] = aif_data->samples[i];
|
||||
}
|
||||
|
||||
int j = 0;
|
||||
for (unsigned int i = aif_data->loop_offset; i < pcm->length; i++)
|
||||
{
|
||||
int pcm_index = aif_data->loop_offset + (j++ % (pcm->length - aif_data->loop_offset));
|
||||
aif->data[pos++] = aif_data->samples[pcm_index];
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
write_bytearray(aif_filename, aif);
|
||||
|
||||
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)
|
||||
{
|
||||
usage();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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 || strcmp(extension, "aiff") == 0)
|
||||
{
|
||||
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, "bin") == 0)
|
||||
{
|
||||
if (argc >= 3)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR("Input file must be .aif or .bin: '%s'\n", input_file);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
1
tools/bin2c/.gitignore
vendored
Normal file
1
tools/bin2c/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
bin2c
|
19
tools/bin2c/LICENSE
Normal file
19
tools/bin2c/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 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.
|
13
tools/bin2c/Makefile
Normal file
13
tools/bin2c/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -s
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
SRCS = bin2c.c
|
||||
|
||||
bin2c: $(SRCS)
|
||||
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) bin2c bin2c.exe
|
201
tools/bin2c/bin2c.c
Normal file
201
tools/bin2c/bin2c.c
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright(c) 2015-2016 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
fprintf(stderr, format, __VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
unsigned char *ReadWholeFile(char *path, int *size)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
|
||||
*size = ftell(fp);
|
||||
|
||||
unsigned char *buffer = malloc(*size);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
|
||||
|
||||
rewind(fp);
|
||||
|
||||
if (fread(buffer, *size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int ExtractData(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");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
FATAL_ERROR("Usage: bin2c INPUT_FILE VAR_NAME [OPTIONS...]\n");
|
||||
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(argv[1], &fileSize);
|
||||
char *var_name = argv[2];
|
||||
int col = 1;
|
||||
int pad = 0;
|
||||
int size = 1;
|
||||
bool isSigned = false;
|
||||
bool isStatic = false;
|
||||
bool isDecimal = false;
|
||||
|
||||
for (int i = 3; i < argc; i++)
|
||||
{
|
||||
if (!strcmp(argv[i], "-col"))
|
||||
{
|
||||
i++;
|
||||
|
||||
if (i >= argc)
|
||||
FATAL_ERROR("Missing argument after '-col'.\n");
|
||||
|
||||
col = atoi(argv[i]);
|
||||
}
|
||||
else if (!strcmp(argv[i], "-pad"))
|
||||
{
|
||||
i++;
|
||||
|
||||
if (i >= argc)
|
||||
FATAL_ERROR("Missing argument after '-pad'.\n");
|
||||
|
||||
pad = atoi(argv[i]);
|
||||
}
|
||||
else if (!strcmp(argv[i], "-size"))
|
||||
{
|
||||
i++;
|
||||
|
||||
if (i >= argc)
|
||||
FATAL_ERROR("Missing argument after '-size'.\n");
|
||||
|
||||
size = atoi(argv[i]);
|
||||
|
||||
if (size != 1 && size != 2 && size != 4)
|
||||
FATAL_ERROR("Size must be 1, 2, or 4.\n");
|
||||
}
|
||||
else if (!strcmp(argv[i], "-signed"))
|
||||
{
|
||||
isSigned = true;
|
||||
isDecimal = true;
|
||||
}
|
||||
else if (!strcmp(argv[i], "-static"))
|
||||
{
|
||||
isStatic = true;
|
||||
}
|
||||
else if (!strcmp(argv[i], "-decimal"))
|
||||
{
|
||||
isDecimal = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR("Unrecognized option '%s'.\n", argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if ((fileSize & (size - 1)) != 0)
|
||||
FATAL_ERROR("Size %d doesn't evenly divide file size %d.\n", size, fileSize);
|
||||
|
||||
printf("// Generated file. Do not edit.\n\n");
|
||||
|
||||
if (isStatic)
|
||||
printf("static ");
|
||||
|
||||
printf("const ");
|
||||
|
||||
if (isSigned)
|
||||
printf("s%d ", 8 * size);
|
||||
else
|
||||
printf("u%d ", 8 * size);
|
||||
|
||||
printf("%s[] =\n{", var_name);
|
||||
|
||||
int count = fileSize / size;
|
||||
int offset = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i % col == 0)
|
||||
printf("\n ");
|
||||
|
||||
int data = ExtractData(buffer, offset, size);
|
||||
offset += size;
|
||||
|
||||
if (isDecimal)
|
||||
{
|
||||
if (isSigned)
|
||||
printf("%*d, ", pad, data);
|
||||
else
|
||||
printf("%*uu, ", pad, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("%#*xu, ", pad, data);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n};\n");
|
||||
|
||||
return 0;
|
||||
}
|
1
tools/gbagfx/.gitignore
vendored
Normal file
1
tools/gbagfx/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
gbagfx
|
19
tools/gbagfx/LICENSE
Normal file
19
tools/gbagfx/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015 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.
|
15
tools/gbagfx/Makefile
Normal file
15
tools/gbagfx/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -s -DPNG_SKIP_SETJMP_CHECK
|
||||
|
||||
LIBS = -lpng -lz
|
||||
|
||||
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 $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
$(RM) gbagfx gbagfx.exe
|
212
tools/gbagfx/convert_png.c
Normal file
212
tools/gbagfx/convert_png.c
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <png.h>
|
||||
#include "global.h"
|
||||
#include "convert_png.h"
|
||||
#include "gfx.h"
|
||||
|
||||
static FILE *PngReadOpen(char *path, png_structp *pngStruct, png_infop *pngInfo)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
unsigned char sig[8];
|
||||
|
||||
if (fread(sig, 8, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read PNG signature from \"%s\".\n", path);
|
||||
|
||||
if (png_sig_cmp(sig, 0, 8))
|
||||
FATAL_ERROR("\"%s\" does not have a valid PNG signature.\n", path);
|
||||
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
|
||||
if (!png_ptr)
|
||||
FATAL_ERROR("Failed to create PNG read struct.\n");
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
|
||||
if (!info_ptr)
|
||||
FATAL_ERROR("Failed to create PNG info struct.\n");
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Failed to init I/O for reading \"%s\".\n", path);
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
png_set_sig_bytes(png_ptr, 8);
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
*pngStruct = png_ptr;
|
||||
*pngInfo = info_ptr;
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
void ReadPng(char *path, struct Image *image)
|
||||
{
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
|
||||
FILE *fp = PngReadOpen(path, &png_ptr, &info_ptr);
|
||||
|
||||
int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
if (bit_depth != image->bitDepth)
|
||||
FATAL_ERROR("\"%s\" has a bit depth of %d, but the expected bit depth is %d.\n", path, bit_depth, image->bitDepth);
|
||||
|
||||
int color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
|
||||
if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_PALETTE)
|
||||
FATAL_ERROR("\"%s\" has an unsupported color type.\n", path);
|
||||
|
||||
// Check if the image has a palette so that we can tell if the colors need to be inverted later.
|
||||
// Don't read the palette because it's not needed for now.
|
||||
image->hasPalette = (color_type == PNG_COLOR_TYPE_PALETTE);
|
||||
|
||||
image->width = png_get_image_width(png_ptr, info_ptr);
|
||||
image->height = png_get_image_height(png_ptr, info_ptr);
|
||||
|
||||
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
||||
|
||||
image->pixels = malloc(image->height * rowbytes);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate pixel buffer.\n");
|
||||
|
||||
png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
|
||||
|
||||
if (row_pointers == NULL)
|
||||
FATAL_ERROR("Failed to allocate row pointers.\n");
|
||||
|
||||
for (int i = 0; i < image->height; i++)
|
||||
row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error reading from \"%s\".\n", path);
|
||||
|
||||
png_read_image(png_ptr, row_pointers);
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
free(row_pointers);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void ReadPngPalette(char *path, struct Palette *palette)
|
||||
{
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
png_colorp colors;
|
||||
int numColors;
|
||||
|
||||
FILE *fp = PngReadOpen(path, &png_ptr, &info_ptr);
|
||||
|
||||
if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE)
|
||||
FATAL_ERROR("The image \"%s\" does not contain a palette.\n", path);
|
||||
|
||||
if (png_get_PLTE(png_ptr, info_ptr, &colors, &numColors) != PNG_INFO_PLTE)
|
||||
FATAL_ERROR("Failed to retrieve palette from \"%s\".\n", path);
|
||||
|
||||
if (numColors > 256)
|
||||
FATAL_ERROR("Images with more than 256 colors are not supported.\n");
|
||||
|
||||
palette->numColors = numColors;
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
palette->colors[i].red = colors[i].red;
|
||||
palette->colors[i].green = colors[i].green;
|
||||
palette->colors[i].blue = colors[i].blue;
|
||||
}
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void SetPngPalette(png_structp png_ptr, png_infop info_ptr, struct Palette *palette)
|
||||
{
|
||||
png_colorp colors = malloc(palette->numColors * sizeof(png_color));
|
||||
|
||||
if (colors == NULL)
|
||||
FATAL_ERROR("Failed to allocate PNG palette.\n");
|
||||
|
||||
for (int i = 0; i < palette->numColors; i++) {
|
||||
colors[i].red = palette->colors[i].red;
|
||||
colors[i].green = palette->colors[i].green;
|
||||
colors[i].blue = palette->colors[i].blue;
|
||||
}
|
||||
|
||||
png_set_PLTE(png_ptr, info_ptr, colors, palette->numColors);
|
||||
|
||||
free(colors);
|
||||
}
|
||||
|
||||
void WritePng(char *path, struct Image *image)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
|
||||
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
|
||||
if (!png_ptr)
|
||||
FATAL_ERROR("Failed to create PNG write struct.\n");
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
|
||||
if (!info_ptr)
|
||||
FATAL_ERROR("Failed to create PNG info struct.\n");
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Failed to init I/O for writing \"%s\".\n", path);
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error writing header for \"%s\".\n", path);
|
||||
|
||||
int color_type = image->hasPalette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY;
|
||||
|
||||
png_set_IHDR(png_ptr, info_ptr, image->width, image->height,
|
||||
image->bitDepth, color_type, PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
|
||||
if (image->hasPalette) {
|
||||
SetPngPalette(png_ptr, info_ptr, &image->palette);
|
||||
|
||||
if (image->hasTransparency) {
|
||||
png_byte trans = 0;
|
||||
png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
|
||||
|
||||
if (row_pointers == NULL)
|
||||
FATAL_ERROR("Failed to allocate row pointers.\n");
|
||||
|
||||
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
||||
|
||||
for (int i = 0; i < image->height; i++)
|
||||
row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error writing \"%s\".\n", path);
|
||||
|
||||
png_write_image(png_ptr, row_pointers);
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error ending write of \"%s\".\n", path);
|
||||
|
||||
png_write_end(png_ptr, NULL);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
free(row_pointers);
|
||||
}
|
12
tools/gbagfx/convert_png.h
Normal file
12
tools/gbagfx/convert_png.h
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef CONVERT_PNG_H
|
||||
#define CONVERT_PNG_H
|
||||
|
||||
#include "gfx.h"
|
||||
|
||||
void ReadPng(char *path, struct Image *image);
|
||||
void WritePng(char *path, struct Image *image);
|
||||
void ReadPngPalette(char *path, struct Palette *palette);
|
||||
|
||||
#endif // CONVERT_PNG_H
|
326
tools/gbagfx/font.c
Normal file
326
tools/gbagfx/font.c
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "font.h"
|
||||
#include "gfx.h"
|
||||
#include "util.h"
|
||||
|
||||
unsigned char gFontPalette[][3] = {
|
||||
{0x90, 0xC8, 0xFF}, // bg (saturated blue that contrasts well with the shadow color)
|
||||
{0x38, 0x38, 0x38}, // fg (dark grey)
|
||||
{0xD8, 0xD8, 0xD8}, // shadow (light grey)
|
||||
{0xFF, 0xFF, 0xFF} // box (white)
|
||||
};
|
||||
|
||||
static void ConvertFromLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
|
||||
{
|
||||
unsigned int srcPixelsOffset = 0;
|
||||
|
||||
for (unsigned int row = 0; row < numRows; row++) {
|
||||
for (unsigned int column = 0; column < 16; column++) {
|
||||
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
|
||||
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
|
||||
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
|
||||
unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
|
||||
|
||||
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
|
||||
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
|
||||
|
||||
srcPixelsOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertToLatinFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
|
||||
{
|
||||
unsigned int destPixelsOffset = 0;
|
||||
|
||||
for (unsigned int row = 0; row < numRows; row++) {
|
||||
for (unsigned int column = 0; column < 16; column++) {
|
||||
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
|
||||
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
|
||||
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
|
||||
unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
|
||||
|
||||
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
|
||||
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
|
||||
|
||||
destPixelsOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertFromHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
|
||||
{
|
||||
for (unsigned int row = 0; row < numRows; row++) {
|
||||
for (unsigned int column = 0; column < 16; column++) {
|
||||
unsigned int glyphIndex = (row * 16) + column;
|
||||
|
||||
for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
|
||||
unsigned int pixelsX = column * 8;
|
||||
unsigned int srcPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
|
||||
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
|
||||
unsigned int destPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
|
||||
|
||||
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
|
||||
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
|
||||
|
||||
srcPixelsOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertToHalfwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
|
||||
{
|
||||
for (unsigned int row = 0; row < numRows; row++) {
|
||||
for (unsigned int column = 0; column < 16; column++) {
|
||||
unsigned int glyphIndex = (row * 16) + column;
|
||||
|
||||
for (unsigned int glyphTile = 0; glyphTile < 2; glyphTile++) {
|
||||
unsigned int pixelsX = column * 8;
|
||||
unsigned int destPixelsOffset = 512 * (glyphIndex >> 4) + 16 * (glyphIndex & 0xF) + 256 * glyphTile;
|
||||
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
unsigned int pixelsY = (row * 16) + (glyphTile * 8) + i;
|
||||
unsigned int srcPixelsOffset = (pixelsY * 32) + (pixelsX / 4);
|
||||
|
||||
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
|
||||
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
|
||||
|
||||
destPixelsOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertFromFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
|
||||
{
|
||||
for (unsigned int row = 0; row < numRows; row++) {
|
||||
for (unsigned int column = 0; column < 16; column++) {
|
||||
unsigned int glyphIndex = (row * 16) + column;
|
||||
|
||||
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
|
||||
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
|
||||
unsigned int srcPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
|
||||
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
|
||||
unsigned int destPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
|
||||
|
||||
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
|
||||
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
|
||||
|
||||
srcPixelsOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertToFullwidthJapaneseFont(unsigned char *src, unsigned char *dest, unsigned int numRows)
|
||||
{
|
||||
for (unsigned int row = 0; row < numRows; row++) {
|
||||
for (unsigned int column = 0; column < 16; column++) {
|
||||
unsigned int glyphIndex = (row * 16) + column;
|
||||
|
||||
for (unsigned int glyphTile = 0; glyphTile < 4; glyphTile++) {
|
||||
unsigned int pixelsX = (column * 16) + ((glyphTile & 1) * 8);
|
||||
unsigned int destPixelsOffset = 512 * (glyphIndex >> 3) + 32 * (glyphIndex & 7) + 256 * (glyphTile >> 1) + 16 * (glyphTile & 1);
|
||||
|
||||
for (unsigned int i = 0; i < 8; i++) {
|
||||
unsigned int pixelsY = (row * 16) + ((glyphTile >> 1) * 8) + i;
|
||||
unsigned int srcPixelsOffset = (pixelsY * 64) + (pixelsX / 4);
|
||||
|
||||
dest[destPixelsOffset] = src[srcPixelsOffset + 1];
|
||||
dest[destPixelsOffset + 1] = src[srcPixelsOffset];
|
||||
|
||||
destPixelsOffset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SetFontPalette(struct Image *image)
|
||||
{
|
||||
image->hasPalette = true;
|
||||
|
||||
image->palette.numColors = 4;
|
||||
|
||||
for (int i = 0; i < image->palette.numColors; i++) {
|
||||
image->palette.colors[i].red = gFontPalette[i][0];
|
||||
image->palette.colors[i].green = gFontPalette[i][1];
|
||||
image->palette.colors[i].blue = gFontPalette[i][2];
|
||||
}
|
||||
|
||||
image->hasTransparency = false;
|
||||
}
|
||||
|
||||
void ReadLatinFont(char *path, struct Image *image)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||
|
||||
int numGlyphs = fileSize / 64;
|
||||
|
||||
if (numGlyphs % 16 != 0)
|
||||
FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
|
||||
|
||||
int numRows = numGlyphs / 16;
|
||||
|
||||
image->width = 256;
|
||||
image->height = numRows * 16;
|
||||
image->bitDepth = 2;
|
||||
image->pixels = malloc(fileSize);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
ConvertFromLatinFont(buffer, image->pixels, numRows);
|
||||
|
||||
free(buffer);
|
||||
|
||||
SetFontPalette(image);
|
||||
}
|
||||
|
||||
void WriteLatinFont(char *path, struct Image *image)
|
||||
{
|
||||
if (image->width != 256)
|
||||
FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
|
||||
|
||||
if (image->height % 16 != 0)
|
||||
FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
|
||||
|
||||
int numRows = image->height / 16;
|
||||
int bufferSize = numRows * 16 * 64;
|
||||
unsigned char *buffer = malloc(bufferSize);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
ConvertToLatinFont(image->pixels, buffer, numRows);
|
||||
|
||||
WriteWholeFile(path, buffer, bufferSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void ReadHalfwidthJapaneseFont(char *path, struct Image *image)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||
|
||||
int glyphSize = 32;
|
||||
|
||||
if (fileSize % glyphSize != 0)
|
||||
FATAL_ERROR("The file size (%d) is not a multiple of %d.\n", fileSize, glyphSize);
|
||||
|
||||
int numGlyphs = fileSize / glyphSize;
|
||||
|
||||
if (numGlyphs % 16 != 0)
|
||||
FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
|
||||
|
||||
int numRows = numGlyphs / 16;
|
||||
|
||||
image->width = 128;
|
||||
image->height = numRows * 16;
|
||||
image->bitDepth = 2;
|
||||
image->pixels = malloc(fileSize);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
ConvertFromHalfwidthJapaneseFont(buffer, image->pixels, numRows);
|
||||
|
||||
free(buffer);
|
||||
|
||||
SetFontPalette(image);
|
||||
}
|
||||
|
||||
void WriteHalfwidthJapaneseFont(char *path, struct Image *image)
|
||||
{
|
||||
if (image->width != 128)
|
||||
FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width);
|
||||
|
||||
if (image->height % 16 != 0)
|
||||
FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
|
||||
|
||||
int numRows = image->height / 16;
|
||||
int bufferSize = numRows * 16 * 32;
|
||||
unsigned char *buffer = malloc(bufferSize);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
ConvertToHalfwidthJapaneseFont(image->pixels, buffer, numRows);
|
||||
|
||||
WriteWholeFile(path, buffer, bufferSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void ReadFullwidthJapaneseFont(char *path, struct Image *image)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||
|
||||
int numGlyphs = fileSize / 64;
|
||||
|
||||
if (numGlyphs % 16 != 0)
|
||||
FATAL_ERROR("The number of glyphs (%d) is not a multiple of 16.\n", numGlyphs);
|
||||
|
||||
int numRows = numGlyphs / 16;
|
||||
|
||||
image->width = 256;
|
||||
image->height = numRows * 16;
|
||||
image->bitDepth = 2;
|
||||
image->pixels = malloc(fileSize);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
ConvertFromFullwidthJapaneseFont(buffer, image->pixels, numRows);
|
||||
|
||||
free(buffer);
|
||||
|
||||
SetFontPalette(image);
|
||||
}
|
||||
|
||||
void WriteFullwidthJapaneseFont(char *path, struct Image *image)
|
||||
{
|
||||
if (image->width != 256)
|
||||
FATAL_ERROR("The width of the font image (%d) is not 256.\n", image->width);
|
||||
|
||||
if (image->height % 16 != 0)
|
||||
FATAL_ERROR("The height of the font image (%d) is not a multiple of 16.\n", image->height);
|
||||
|
||||
int numRows = image->height / 16;
|
||||
int bufferSize = numRows * 16 * 64;
|
||||
unsigned char *buffer = malloc(bufferSize);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
ConvertToFullwidthJapaneseFont(image->pixels, buffer, numRows);
|
||||
|
||||
WriteWholeFile(path, buffer, bufferSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
16
tools/gbagfx/font.h
Normal file
16
tools/gbagfx/font.h
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "gfx.h"
|
||||
|
||||
void ReadLatinFont(char *path, struct Image *image);
|
||||
void WriteLatinFont(char *path, struct Image *image);
|
||||
void ReadHalfwidthJapaneseFont(char *path, struct Image *image);
|
||||
void WriteHalfwidthJapaneseFont(char *path, struct Image *image);
|
||||
void ReadFullwidthJapaneseFont(char *path, struct Image *image);
|
||||
void WriteFullwidthJapaneseFont(char *path, struct Image *image);
|
||||
|
||||
#endif // FONT_H
|
329
tools/gbagfx/gfx.c
Normal file
329
tools/gbagfx/gfx.c
Normal file
@ -0,0 +1,329 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "gfx.h"
|
||||
#include "util.h"
|
||||
|
||||
#define GET_GBA_PAL_RED(x) (((x) >> 0) & 0x1F)
|
||||
#define GET_GBA_PAL_GREEN(x) (((x) >> 5) & 0x1F)
|
||||
#define GET_GBA_PAL_BLUE(x) (((x) >> 10) & 0x1F)
|
||||
|
||||
#define SET_GBA_PAL(r, g, b) (((b) << 10) | ((g) << 5) | (r))
|
||||
|
||||
#define UPCONVERT_BIT_DEPTH(x) (((x) * 255) / 31)
|
||||
|
||||
#define DOWNCONVERT_BIT_DEPTH(x) ((x) / 8)
|
||||
|
||||
static void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
|
||||
{
|
||||
int tilesX = 0;
|
||||
int tilesY = 0;
|
||||
int pitch = tilesWidth;
|
||||
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int destY = tilesY * 8 + j;
|
||||
int destX = tilesX;
|
||||
unsigned char srcPixelOctet = *src++;
|
||||
unsigned char *destPixelOctet = &dest[destY * pitch + destX];
|
||||
|
||||
for (int k = 0; k < 8; k++) {
|
||||
*destPixelOctet <<= 1;
|
||||
*destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
|
||||
srcPixelOctet >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
tilesX++;
|
||||
|
||||
if (tilesX == tilesWidth) {
|
||||
tilesX = 0;
|
||||
tilesY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
|
||||
{
|
||||
int tilesX = 0;
|
||||
int tilesY = 0;
|
||||
int pitch = tilesWidth * 4;
|
||||
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int destY = tilesY * 8 + j;
|
||||
|
||||
for (int k = 0; k < 4; k++) {
|
||||
int destX = tilesX * 4 + k;
|
||||
unsigned char srcPixelPair = *src++;
|
||||
unsigned char leftPixel = srcPixelPair & 0xF;
|
||||
unsigned char rightPixel = srcPixelPair >> 4;
|
||||
|
||||
if (invertColors) {
|
||||
leftPixel = 15 - leftPixel;
|
||||
rightPixel = 15 - rightPixel;
|
||||
}
|
||||
|
||||
dest[destY * pitch + destX] = (leftPixel << 4) | rightPixel;
|
||||
}
|
||||
}
|
||||
|
||||
tilesX++;
|
||||
|
||||
if (tilesX == tilesWidth) {
|
||||
tilesX = 0;
|
||||
tilesY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
|
||||
{
|
||||
int tilesX = 0;
|
||||
int tilesY = 0;
|
||||
int pitch = tilesWidth * 8;
|
||||
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int destY = tilesY * 8 + j;
|
||||
|
||||
for (int k = 0; k < 8; k++) {
|
||||
int destX = tilesX * 8 + k;
|
||||
unsigned char srcPixel = *src++;
|
||||
|
||||
if (invertColors)
|
||||
srcPixel = 255 - srcPixel;
|
||||
|
||||
dest[destY * pitch + destX] = srcPixel;
|
||||
}
|
||||
}
|
||||
|
||||
tilesX++;
|
||||
|
||||
if (tilesX == tilesWidth) {
|
||||
tilesX = 0;
|
||||
tilesY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
|
||||
{
|
||||
int tilesX = 0;
|
||||
int tilesY = 0;
|
||||
int pitch = tilesWidth;
|
||||
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int srcY = tilesY * 8 + j;
|
||||
int srcX = tilesX;
|
||||
unsigned char srcPixelOctet = src[srcY * pitch + srcX];
|
||||
unsigned char *destPixelOctet = dest++;
|
||||
|
||||
for (int k = 0; k < 8; k++) {
|
||||
*destPixelOctet <<= 1;
|
||||
*destPixelOctet |= (srcPixelOctet & 1) ^ invertColors;
|
||||
srcPixelOctet >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
tilesX++;
|
||||
|
||||
if (tilesX == tilesWidth) {
|
||||
tilesX = 0;
|
||||
tilesY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
|
||||
{
|
||||
int tilesX = 0;
|
||||
int tilesY = 0;
|
||||
int pitch = tilesWidth * 4;
|
||||
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int srcY = tilesY * 8 + j;
|
||||
|
||||
for (int k = 0; k < 4; k++) {
|
||||
int srcX = tilesX * 4 + k;
|
||||
unsigned char srcPixelPair = src[srcY * pitch + srcX];
|
||||
unsigned char leftPixel = srcPixelPair >> 4;
|
||||
unsigned char rightPixel = srcPixelPair & 0xF;
|
||||
|
||||
if (invertColors) {
|
||||
leftPixel = 15 - leftPixel;
|
||||
rightPixel = 15 - rightPixel;
|
||||
}
|
||||
|
||||
*dest++ = (rightPixel << 4) | leftPixel;
|
||||
}
|
||||
}
|
||||
|
||||
tilesX++;
|
||||
|
||||
if (tilesX == tilesWidth) {
|
||||
tilesX = 0;
|
||||
tilesY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int tilesWidth, bool invertColors)
|
||||
{
|
||||
int tilesX = 0;
|
||||
int tilesY = 0;
|
||||
int pitch = tilesWidth * 8;
|
||||
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int srcY = tilesY * 8 + j;
|
||||
|
||||
for (int k = 0; k < 8; k++) {
|
||||
int srcX = tilesX * 8 + k;
|
||||
unsigned char srcPixel = src[srcY * pitch + srcX];
|
||||
|
||||
if (invertColors)
|
||||
srcPixel = 255 - srcPixel;
|
||||
|
||||
*dest++ = srcPixel;
|
||||
}
|
||||
}
|
||||
|
||||
tilesX++;
|
||||
|
||||
if (tilesX == tilesWidth) {
|
||||
tilesX = 0;
|
||||
tilesY++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadImage(char *path, int tilesWidth, int bitDepth, struct Image *image, bool invertColors)
|
||||
{
|
||||
int tileSize = bitDepth * 8;
|
||||
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||
|
||||
int numTiles = fileSize / tileSize;
|
||||
|
||||
int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
|
||||
|
||||
image->width = tilesWidth * 8;
|
||||
image->height = tilesHeight * 8;
|
||||
image->bitDepth = bitDepth;
|
||||
image->pixels = calloc(tilesWidth * tilesHeight, tileSize);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for pixels.\n");
|
||||
|
||||
switch (bitDepth) {
|
||||
case 1:
|
||||
ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, tilesWidth, invertColors);
|
||||
break;
|
||||
case 4:
|
||||
ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, tilesWidth, invertColors);
|
||||
break;
|
||||
case 8:
|
||||
ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, tilesWidth, invertColors);
|
||||
break;
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void WriteImage(char *path, int numTiles, int bitDepth, struct Image *image, bool invertColors)
|
||||
{
|
||||
int tileSize = bitDepth * 8;
|
||||
|
||||
if (image->width % 8 != 0)
|
||||
FATAL_ERROR("The width in pixels (%d) isn't a multiple of 8.\n", image->width);
|
||||
|
||||
if (image->height % 8 != 0)
|
||||
FATAL_ERROR("The height in pixels (%d) isn't a multiple of 8.\n", image->height);
|
||||
|
||||
int tilesWidth = image->width / 8;
|
||||
int tilesHeight = image->height / 8;
|
||||
|
||||
int maxNumTiles = tilesWidth * tilesHeight;
|
||||
|
||||
if (numTiles == 0)
|
||||
numTiles = maxNumTiles;
|
||||
else if (numTiles > maxNumTiles)
|
||||
FATAL_ERROR("The specified number of tiles (%d) is greater than the maximum possible value (%d).\n", numTiles, maxNumTiles);
|
||||
|
||||
int bufferSize = numTiles * tileSize;
|
||||
unsigned char *buffer = malloc(bufferSize);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for pixels.\n");
|
||||
|
||||
switch (bitDepth) {
|
||||
case 1:
|
||||
ConvertToTiles1Bpp(image->pixels, buffer, numTiles, tilesWidth, invertColors);
|
||||
break;
|
||||
case 4:
|
||||
ConvertToTiles4Bpp(image->pixels, buffer, numTiles, tilesWidth, invertColors);
|
||||
break;
|
||||
case 8:
|
||||
ConvertToTiles8Bpp(image->pixels, buffer, numTiles, tilesWidth, invertColors);
|
||||
break;
|
||||
}
|
||||
|
||||
WriteWholeFile(path, buffer, bufferSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void FreeImage(struct Image *image)
|
||||
{
|
||||
free(image->pixels);
|
||||
image->pixels = NULL;
|
||||
}
|
||||
|
||||
void ReadGbaPalette(char *path, struct Palette *palette)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *data = ReadWholeFile(path, &fileSize);
|
||||
|
||||
if (fileSize % 2 != 0)
|
||||
FATAL_ERROR("The file size (%d) is not a multiple of 2.\n", fileSize);
|
||||
|
||||
palette->numColors = fileSize / 2;
|
||||
|
||||
for (int i = 0; i < palette->numColors; i++) {
|
||||
uint16_t paletteEntry = (data[i * 2 + 1] << 8) | data[i * 2];
|
||||
palette->colors[i].red = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_RED(paletteEntry));
|
||||
palette->colors[i].green = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_GREEN(paletteEntry));
|
||||
palette->colors[i].blue = UPCONVERT_BIT_DEPTH(GET_GBA_PAL_BLUE(paletteEntry));
|
||||
}
|
||||
|
||||
free(data);
|
||||
}
|
||||
|
||||
void WriteGbaPalette(char *path, struct Palette *palette)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
|
||||
|
||||
for (int i = 0; i < palette->numColors; i++) {
|
||||
unsigned char red = DOWNCONVERT_BIT_DEPTH(palette->colors[i].red);
|
||||
unsigned char green = DOWNCONVERT_BIT_DEPTH(palette->colors[i].green);
|
||||
unsigned char blue = DOWNCONVERT_BIT_DEPTH(palette->colors[i].blue);
|
||||
|
||||
uint16_t paletteEntry = SET_GBA_PAL(red, green, blue);
|
||||
|
||||
fputc(paletteEntry & 0xFF, fp);
|
||||
fputc(paletteEntry >> 8, fp);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
36
tools/gbagfx/gfx.h
Normal file
36
tools/gbagfx/gfx.h
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef GFX_H
|
||||
#define GFX_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct Color {
|
||||
unsigned char red;
|
||||
unsigned char green;
|
||||
unsigned char blue;
|
||||
};
|
||||
|
||||
struct Palette {
|
||||
struct Color colors[256];
|
||||
int numColors;
|
||||
};
|
||||
|
||||
struct Image {
|
||||
int width;
|
||||
int height;
|
||||
int bitDepth;
|
||||
unsigned char *pixels;
|
||||
bool hasPalette;
|
||||
struct Palette palette;
|
||||
bool hasTransparency;
|
||||
};
|
||||
|
||||
void ReadImage(char *path, int tilesWidth, int bitDepth, struct Image *image, bool invertColors);
|
||||
void WriteImage(char *path, int numTiles, int bitDepth, struct Image *image, bool invertColors);
|
||||
void FreeImage(struct Image *image);
|
||||
void ReadGbaPalette(char *path, struct Palette *palette);
|
||||
void WriteGbaPalette(char *path, struct Palette *palette);
|
||||
|
||||
#endif // GFX_H
|
31
tools/gbagfx/global.h
Normal file
31
tools/gbagfx/global.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef GLOBAL_H
|
||||
#define GLOBAL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do { \
|
||||
fprintf(stderr, format, __VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#define UNUSED
|
||||
|
||||
#else
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do { \
|
||||
fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#define UNUSED __attribute__((__unused__))
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
#endif // GLOBAL_H
|
172
tools/gbagfx/jasc_pal.c
Normal file
172
tools/gbagfx/jasc_pal.c
Normal file
@ -0,0 +1,172 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "global.h"
|
||||
#include "gfx.h"
|
||||
#include "util.h"
|
||||
|
||||
// Read/write Paint Shop Pro palette files.
|
||||
|
||||
// Format of a Paint Shop Pro palette file, line by line:
|
||||
// "JASC-PAL\r\n" (signature)
|
||||
// "0100\r\n" (version; seems to always be "0100")
|
||||
// "<NUMBER_OF_COLORS>\r\n" (number of colors in decimal)
|
||||
//
|
||||
// <NUMBER_OF_COLORS> times:
|
||||
// "<RED> <GREEN> <BLUE>\r\n" (color entry)
|
||||
//
|
||||
// Each color component is a decimal number from 0 to 255.
|
||||
// Examples:
|
||||
// Black - "0 0 0\r\n"
|
||||
// Blue - "0 0 255\r\n"
|
||||
// Brown - "150 75 0\r\n"
|
||||
|
||||
#define MAX_LINE_LENGTH 11
|
||||
|
||||
void ReadJascPaletteLine(FILE *fp, char *line)
|
||||
{
|
||||
int c;
|
||||
int length = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
c = fgetc(fp);
|
||||
|
||||
if (c == '\r')
|
||||
{
|
||||
c = fgetc(fp);
|
||||
|
||||
if (c != '\n')
|
||||
FATAL_ERROR("CR line endings aren't supported.\n");
|
||||
|
||||
line[length] = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
FATAL_ERROR("LF line endings aren't supported.\n");
|
||||
|
||||
if (c == EOF)
|
||||
FATAL_ERROR("Unexpected EOF. No CRLF at end of file.\n");
|
||||
|
||||
if (c == 0)
|
||||
FATAL_ERROR("NUL character in file.\n");
|
||||
|
||||
if (length == MAX_LINE_LENGTH)
|
||||
{
|
||||
line[length] = 0;
|
||||
FATAL_ERROR("The line \"%s\" is too long.\n", line);
|
||||
}
|
||||
|
||||
line[length++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadJascPalette(char *path, struct Palette *palette)
|
||||
{
|
||||
char line[MAX_LINE_LENGTH + 1];
|
||||
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open JASC-PAL file \"%s\" for reading.\n", path);
|
||||
|
||||
ReadJascPaletteLine(fp, line);
|
||||
|
||||
if (strcmp(line, "JASC-PAL") != 0)
|
||||
FATAL_ERROR("Invalid JASC-PAL signature.\n");
|
||||
|
||||
ReadJascPaletteLine(fp, line);
|
||||
|
||||
if (strcmp(line, "0100") != 0)
|
||||
FATAL_ERROR("Unsuported JASC-PAL version.\n");
|
||||
|
||||
ReadJascPaletteLine(fp, line);
|
||||
|
||||
if (!ParseNumber(line, NULL, 10, &palette->numColors))
|
||||
FATAL_ERROR("Failed to parse number of colors.\n");
|
||||
|
||||
if (palette->numColors < 1 || palette->numColors > 256)
|
||||
FATAL_ERROR("%d is an invalid number of colors. The number of colors must be in the range [1, 256].\n", palette->numColors);
|
||||
|
||||
for (int i = 0; i < palette->numColors; i++)
|
||||
{
|
||||
ReadJascPaletteLine(fp, line);
|
||||
|
||||
char *s = line;
|
||||
char *end;
|
||||
|
||||
int red;
|
||||
int green;
|
||||
int blue;
|
||||
|
||||
if (!ParseNumber(s, &end, 10, &red))
|
||||
FATAL_ERROR("Failed to parse red color component.\n");
|
||||
|
||||
s = end;
|
||||
|
||||
if (*s != ' ')
|
||||
FATAL_ERROR("Expected a space after red color component.\n");
|
||||
|
||||
s++;
|
||||
|
||||
if (*s < '0' || *s > '9')
|
||||
FATAL_ERROR("Expected only a space between red and green color components.\n");
|
||||
|
||||
if (!ParseNumber(s, &end, 10, &green))
|
||||
FATAL_ERROR("Failed to parse green color component.\n");
|
||||
|
||||
s = end;
|
||||
|
||||
if (*s != ' ')
|
||||
FATAL_ERROR("Expected a space after green color component.\n");
|
||||
|
||||
s++;
|
||||
|
||||
if (*s < '0' || *s > '9')
|
||||
FATAL_ERROR("Expected only a space between green and blue color components.\n");
|
||||
|
||||
if (!ParseNumber(s, &end, 10, &blue))
|
||||
FATAL_ERROR("Failed to parse blue color component.\n");
|
||||
|
||||
if (*end != 0)
|
||||
FATAL_ERROR("Garbage after blue color component.\n");
|
||||
|
||||
if (red < 0 || red > 255)
|
||||
FATAL_ERROR("Red color component (%d) is outside the range [0, 255].\n", red);
|
||||
|
||||
if (green < 0 || green > 255)
|
||||
FATAL_ERROR("Green color component (%d) is outside the range [0, 255].\n", green);
|
||||
|
||||
if (blue < 0 || blue > 255)
|
||||
FATAL_ERROR("Blue color component (%d) is outside the range [0, 255].\n", blue);
|
||||
|
||||
palette->colors[i].red = red;
|
||||
palette->colors[i].green = green;
|
||||
palette->colors[i].blue = blue;
|
||||
}
|
||||
|
||||
if (fgetc(fp) != EOF)
|
||||
FATAL_ERROR("Garbage after color data.\n");
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void WriteJascPalette(char *path, struct Palette *palette)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
fputs("JASC-PAL\r\n", fp);
|
||||
fputs("0100\r\n", fp);
|
||||
fprintf(fp, "%d\r\n", palette->numColors);
|
||||
|
||||
for (int i = 0; i < palette->numColors; i++)
|
||||
{
|
||||
struct Color *color = &palette->colors[i];
|
||||
fprintf(fp, "%d %d %d\r\n", color->red, color->green, color->blue);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
9
tools/gbagfx/jasc_pal.h
Normal file
9
tools/gbagfx/jasc_pal.h
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef JASC_PAL_H
|
||||
#define JASC_PAL_H
|
||||
|
||||
void ReadJascPalette(char *path, struct Palette *palette);
|
||||
void WriteJascPalette(char *path, struct Palette *palette);
|
||||
|
||||
#endif // JASC_PAL_H
|
155
tools/gbagfx/lz.c
Normal file
155
tools/gbagfx/lz.c
Normal file
@ -0,0 +1,155 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "lz.h"
|
||||
|
||||
unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize)
|
||||
{
|
||||
if (srcSize < 4)
|
||||
goto fail;
|
||||
|
||||
int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
|
||||
|
||||
unsigned char *dest = malloc(destSize);
|
||||
|
||||
if (dest == NULL)
|
||||
goto fail;
|
||||
|
||||
int srcPos = 4;
|
||||
int destPos = 0;
|
||||
|
||||
for (;;) {
|
||||
if (srcPos >= srcSize)
|
||||
goto fail;
|
||||
|
||||
unsigned char flags = src[srcPos++];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (flags & 0x80) {
|
||||
if (srcPos + 1 >= srcSize)
|
||||
goto fail;
|
||||
|
||||
int blockSize = (src[srcPos] >> 4) + 3;
|
||||
int blockDistance = (((src[srcPos] & 0xF) << 8) | src[srcPos + 1]) + 1;
|
||||
|
||||
srcPos += 2;
|
||||
|
||||
int blockPos = destPos - blockDistance;
|
||||
|
||||
// Some Ruby/Sapphire tilesets overflow.
|
||||
if (destPos + blockSize > destSize) {
|
||||
blockSize = destSize - destPos;
|
||||
fprintf(stderr, "Destination buffer overflow.\n");
|
||||
}
|
||||
|
||||
if (blockPos < 0)
|
||||
goto fail;
|
||||
|
||||
for (int j = 0; j < blockSize; j++)
|
||||
dest[destPos++] = dest[blockPos + j];
|
||||
} else {
|
||||
if (srcPos >= srcSize || destPos >= destSize)
|
||||
goto fail;
|
||||
|
||||
dest[destPos++] = src[srcPos++];
|
||||
}
|
||||
|
||||
if (destPos == destSize) {
|
||||
*uncompressedSize = destSize;
|
||||
return dest;
|
||||
}
|
||||
|
||||
flags <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
FATAL_ERROR("Fatal error while decompressing LZ file.\n");
|
||||
}
|
||||
|
||||
unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize)
|
||||
{
|
||||
const int minDistance = 2; // for compatibility with LZ77UnCompVram()
|
||||
|
||||
if (srcSize <= 0)
|
||||
goto fail;
|
||||
|
||||
int worstCaseDestSize = 4 + srcSize + ((srcSize + 7) / 8);
|
||||
|
||||
// Round up to the next multiple of four.
|
||||
worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
|
||||
|
||||
unsigned char *dest = malloc(worstCaseDestSize);
|
||||
|
||||
if (dest == NULL)
|
||||
goto fail;
|
||||
|
||||
// header
|
||||
dest[0] = 0x10; // LZ compression type
|
||||
dest[1] = (unsigned char)srcSize;
|
||||
dest[2] = (unsigned char)(srcSize >> 8);
|
||||
dest[3] = (unsigned char)(srcSize >> 16);
|
||||
|
||||
int srcPos = 0;
|
||||
int destPos = 4;
|
||||
|
||||
for (;;) {
|
||||
unsigned char *flags = &dest[destPos++];
|
||||
*flags = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int bestBlockDistance = 0;
|
||||
int bestBlockSize = 0;
|
||||
int blockDistance = minDistance;
|
||||
|
||||
while (blockDistance <= srcPos && blockDistance <= 0x1000) {
|
||||
int blockStart = srcPos - blockDistance;
|
||||
int blockSize = 0;
|
||||
|
||||
while (blockSize < 18
|
||||
&& srcPos + blockSize < srcSize
|
||||
&& src[blockStart + blockSize] == src[srcPos + blockSize])
|
||||
blockSize++;
|
||||
|
||||
if (blockSize > bestBlockSize) {
|
||||
bestBlockDistance = blockDistance;
|
||||
bestBlockSize = blockSize;
|
||||
|
||||
if (blockSize == 18)
|
||||
break;
|
||||
}
|
||||
|
||||
blockDistance++;
|
||||
}
|
||||
|
||||
if (bestBlockSize >= 3) {
|
||||
*flags |= (0x80 >> i);
|
||||
srcPos += bestBlockSize;
|
||||
bestBlockSize -= 3;
|
||||
bestBlockDistance--;
|
||||
dest[destPos++] = (bestBlockSize << 4) | ((unsigned int)bestBlockDistance >> 8);
|
||||
dest[destPos++] = (unsigned char)bestBlockDistance;
|
||||
} else {
|
||||
dest[destPos++] = src[srcPos++];
|
||||
}
|
||||
|
||||
if (srcPos == srcSize) {
|
||||
// Pad to multiple of 4 bytes.
|
||||
int remainder = destPos % 4;
|
||||
|
||||
if (remainder != 0) {
|
||||
for (int i = 0; i < 4 - remainder; i++)
|
||||
dest[destPos++] = 0;
|
||||
}
|
||||
|
||||
*compressedSize = destPos;
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
FATAL_ERROR("Fatal error while compressing LZ file.\n");
|
||||
}
|
9
tools/gbagfx/lz.h
Normal file
9
tools/gbagfx/lz.h
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef LZ_H
|
||||
#define LZ_H
|
||||
|
||||
unsigned char *LZDecompress(unsigned char *src, int srcSize, int *uncompressedSize);
|
||||
unsigned char *LZCompress(unsigned char *src, int srcSize, int *compressedSize);
|
||||
|
||||
#endif // LZ_H
|
402
tools/gbagfx/main.c
Normal file
402
tools/gbagfx/main.c
Normal file
@ -0,0 +1,402 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "util.h"
|
||||
#include "gfx.h"
|
||||
#include "convert_png.h"
|
||||
#include "jasc_pal.h"
|
||||
#include "lz.h"
|
||||
#include "rl.h"
|
||||
#include "font.h"
|
||||
|
||||
struct CommandHandler
|
||||
{
|
||||
const char *inputFileExtension;
|
||||
const char *outputFileExtension;
|
||||
void(*function)(char *inputPath, char *outputPath, int argc, char **argv);
|
||||
};
|
||||
|
||||
void ConvertGbaToPng(char *inputPath, char *outputPath, int width, int bitDepth, char *paletteFilePath, bool hasTransparency)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
if (paletteFilePath != NULL)
|
||||
{
|
||||
ReadGbaPalette(paletteFilePath, &image.palette);
|
||||
image.hasPalette = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
image.hasPalette = false;
|
||||
}
|
||||
|
||||
ReadImage(inputPath, width, bitDepth, &image, !image.hasPalette);
|
||||
|
||||
image.hasTransparency = hasTransparency;
|
||||
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void ConvertPngToGba(char *inputPath, char *outputPath, int numTiles, int bitDepth)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.bitDepth = bitDepth;
|
||||
|
||||
ReadPng(inputPath, &image);
|
||||
|
||||
WriteImage(outputPath, numTiles, bitDepth, &image, !image.hasPalette);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
||||
{
|
||||
char *inputFileExtension = GetFileExtension(inputPath);
|
||||
int bitDepth = inputFileExtension[0] - '0';
|
||||
char *paletteFilePath = NULL;
|
||||
bool hasTransparency = false;
|
||||
int width = 1;
|
||||
|
||||
for (int i = 3; i < argc; i++)
|
||||
{
|
||||
char *option = argv[i];
|
||||
|
||||
if (strcmp(option, "-palette") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No palette file path following \"-palette\".\n");
|
||||
|
||||
i++;
|
||||
|
||||
paletteFilePath = argv[i];
|
||||
}
|
||||
else if (strcmp(option, "-object") == 0)
|
||||
{
|
||||
hasTransparency = true;
|
||||
}
|
||||
else if (strcmp(option, "-width") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No width following \"-width\".\n");
|
||||
|
||||
i++;
|
||||
|
||||
if (!ParseNumber(argv[i], NULL, 10, &width))
|
||||
FATAL_ERROR("Failed to parse width.\n");
|
||||
|
||||
if (width < 1)
|
||||
FATAL_ERROR("Width must be positive.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
||||
}
|
||||
}
|
||||
|
||||
ConvertGbaToPng(inputPath, outputPath, width, bitDepth, paletteFilePath, hasTransparency);
|
||||
}
|
||||
|
||||
void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
||||
{
|
||||
char *outputFileExtension = GetFileExtension(outputPath);
|
||||
int bitDepth = outputFileExtension[0] - '0';
|
||||
int numTiles = 0;
|
||||
|
||||
for (int i = 3; i < argc; i++)
|
||||
{
|
||||
char *option = argv[i];
|
||||
|
||||
if (strcmp(option, "-num_tiles") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No number of tiles following \"-num_tiles\".\n");
|
||||
|
||||
i++;
|
||||
|
||||
if (!ParseNumber(argv[i], NULL, 10, &numTiles))
|
||||
FATAL_ERROR("Failed to parse number of tiles.\n");
|
||||
|
||||
if (numTiles < 1)
|
||||
FATAL_ERROR("Number of tiles must be positive.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
||||
}
|
||||
}
|
||||
|
||||
ConvertPngToGba(inputPath, outputPath, numTiles, bitDepth);
|
||||
}
|
||||
|
||||
void HandlePngToGbaPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Palette palette;
|
||||
|
||||
ReadPngPalette(inputPath, &palette);
|
||||
WriteGbaPalette(outputPath, &palette);
|
||||
}
|
||||
|
||||
void HandleGbaToJascPaletteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Palette palette;
|
||||
|
||||
ReadGbaPalette(inputPath, &palette);
|
||||
WriteJascPalette(outputPath, &palette);
|
||||
}
|
||||
|
||||
void HandleJascToGbaPaletteCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
||||
{
|
||||
int numColors = 0;
|
||||
|
||||
for (int i = 3; i < argc; i++)
|
||||
{
|
||||
char *option = argv[i];
|
||||
|
||||
if (strcmp(option, "-num_colors") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No number of colors following \"-num_colors\".\n");
|
||||
|
||||
i++;
|
||||
|
||||
if (!ParseNumber(argv[i], NULL, 10, &numColors))
|
||||
FATAL_ERROR("Failed to parse number of colors.\n");
|
||||
|
||||
if (numColors < 1)
|
||||
FATAL_ERROR("Number of colors must be positive.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
||||
}
|
||||
}
|
||||
|
||||
struct Palette palette;
|
||||
|
||||
ReadJascPalette(inputPath, &palette);
|
||||
|
||||
if (numColors != 0)
|
||||
palette.numColors = numColors;
|
||||
|
||||
WriteGbaPalette(outputPath, &palette);
|
||||
}
|
||||
|
||||
void HandleLatinFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
ReadLatinFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.bitDepth = 2;
|
||||
|
||||
ReadPng(inputPath, &image);
|
||||
WriteLatinFont(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandleHalfwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
ReadHalfwidthJapaneseFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.bitDepth = 2;
|
||||
|
||||
ReadPng(inputPath, &image);
|
||||
WriteHalfwidthJapaneseFont(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandleFullwidthJapaneseFontToPngCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
ReadFullwidthJapaneseFont(inputPath, &image);
|
||||
WritePng(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
struct Image image;
|
||||
|
||||
image.bitDepth = 2;
|
||||
|
||||
ReadPng(inputPath, &image);
|
||||
WriteFullwidthJapaneseFont(outputPath, &image);
|
||||
|
||||
FreeImage(&image);
|
||||
}
|
||||
|
||||
void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv)
|
||||
{
|
||||
int overflowSize = 0;
|
||||
|
||||
for (int i = 3; i < argc; i++)
|
||||
{
|
||||
char *option = argv[i];
|
||||
|
||||
if (strcmp(option, "-overflow") == 0)
|
||||
{
|
||||
if (i + 1 >= argc)
|
||||
FATAL_ERROR("No size following \"-overflow\".\n");
|
||||
|
||||
i++;
|
||||
|
||||
if (!ParseNumber(argv[i], NULL, 10, &overflowSize))
|
||||
FATAL_ERROR("Failed to parse overflow size.\n");
|
||||
|
||||
if (overflowSize < 1)
|
||||
FATAL_ERROR("Overflow size must be positive.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
|
||||
}
|
||||
}
|
||||
|
||||
// The overflow option allows a quirk in some of Ruby/Sapphire's tilesets
|
||||
// to be reproduced. It works by appending a number of zeros to the data
|
||||
// before compressing it and then amending the LZ header's size field to
|
||||
// reflect the expected size. This will cause an overflow when decompressing
|
||||
// the data.
|
||||
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFileZeroPadded(inputPath, &fileSize, overflowSize);
|
||||
|
||||
int compressedSize;
|
||||
unsigned char *compressedData = LZCompress(buffer, fileSize + overflowSize, &compressedSize);
|
||||
|
||||
compressedData[1] = (unsigned char)fileSize;
|
||||
compressedData[2] = (unsigned char)(fileSize >> 8);
|
||||
compressedData[3] = (unsigned char)(fileSize >> 16);
|
||||
|
||||
free(buffer);
|
||||
|
||||
WriteWholeFile(outputPath, compressedData, compressedSize);
|
||||
|
||||
free(compressedData);
|
||||
}
|
||||
|
||||
void HandleLZDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
|
||||
|
||||
int uncompressedSize;
|
||||
unsigned char *uncompressedData = LZDecompress(buffer, fileSize, &uncompressedSize);
|
||||
|
||||
free(buffer);
|
||||
|
||||
WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
|
||||
|
||||
free(uncompressedData);
|
||||
}
|
||||
|
||||
void HandleRLCompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
|
||||
|
||||
int compressedSize;
|
||||
unsigned char *compressedData = RLCompress(buffer, fileSize, &compressedSize);
|
||||
|
||||
free(buffer);
|
||||
|
||||
WriteWholeFile(outputPath, compressedData, compressedSize);
|
||||
|
||||
free(compressedData);
|
||||
}
|
||||
|
||||
void HandleRLDecompressCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(inputPath, &fileSize);
|
||||
|
||||
int uncompressedSize;
|
||||
unsigned char *uncompressedData = RLDecompress(buffer, fileSize, &uncompressedSize);
|
||||
|
||||
free(buffer);
|
||||
|
||||
WriteWholeFile(outputPath, uncompressedData, uncompressedSize);
|
||||
|
||||
free(uncompressedData);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
FATAL_ERROR("Usage: gbagfx INPUT_PATH OUTPUT_PATH [options...]\n");
|
||||
|
||||
struct CommandHandler handlers[] =
|
||||
{
|
||||
{ "1bpp", "png", HandleGbaToPngCommand },
|
||||
{ "4bpp", "png", HandleGbaToPngCommand },
|
||||
{ "8bpp", "png", HandleGbaToPngCommand },
|
||||
{ "png", "1bpp", HandlePngToGbaCommand },
|
||||
{ "png", "4bpp", HandlePngToGbaCommand },
|
||||
{ "png", "8bpp", HandlePngToGbaCommand },
|
||||
{ "png", "gbapal", HandlePngToGbaPaletteCommand },
|
||||
{ "gbapal", "pal", HandleGbaToJascPaletteCommand },
|
||||
{ "pal", "gbapal", HandleJascToGbaPaletteCommand },
|
||||
{ "latfont", "png", HandleLatinFontToPngCommand },
|
||||
{ "png", "latfont", HandlePngToLatinFontCommand },
|
||||
{ "hwjpnfont", "png", HandleHalfwidthJapaneseFontToPngCommand },
|
||||
{ "png", "hwjpnfont", HandlePngToHalfwidthJapaneseFontCommand },
|
||||
{ "fwjpnfont", "png", HandleFullwidthJapaneseFontToPngCommand },
|
||||
{ "png", "fwjpnfont", HandlePngToFullwidthJapaneseFontCommand },
|
||||
{ NULL, "lz", HandleLZCompressCommand },
|
||||
{ "lz", NULL, HandleLZDecompressCommand },
|
||||
{ NULL, "rl", HandleRLCompressCommand },
|
||||
{ "rl", NULL, HandleRLDecompressCommand },
|
||||
{ NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
char *inputPath = argv[1];
|
||||
char *outputPath = argv[2];
|
||||
char *inputFileExtension = GetFileExtension(inputPath);
|
||||
char *outputFileExtension = GetFileExtension(outputPath);
|
||||
|
||||
if (inputFileExtension == NULL)
|
||||
FATAL_ERROR("Input file \"%s\" has no extension.\n", inputPath);
|
||||
|
||||
if (outputFileExtension == NULL)
|
||||
FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
|
||||
|
||||
for (int i = 0; handlers[i].function != NULL; i++)
|
||||
{
|
||||
if ((handlers[i].inputFileExtension == NULL || strcmp(handlers[i].inputFileExtension, inputFileExtension) == 0)
|
||||
&& (handlers[i].outputFileExtension == NULL || strcmp(handlers[i].outputFileExtension, outputFileExtension) == 0))
|
||||
{
|
||||
handlers[i].function(inputPath, outputPath, argc, argv);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", inputPath, outputPath);
|
||||
}
|
149
tools/gbagfx/rl.c
Normal file
149
tools/gbagfx/rl.c
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (c) 2016 YamaArashi
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "rl.h"
|
||||
|
||||
unsigned char *RLDecompress(unsigned char *src, int srcSize, int *uncompressedSize)
|
||||
{
|
||||
if (srcSize < 4)
|
||||
goto fail;
|
||||
|
||||
int destSize = (src[3] << 16) | (src[2] << 8) | src[1];
|
||||
|
||||
unsigned char *dest = malloc(destSize);
|
||||
|
||||
if (dest == NULL)
|
||||
goto fail;
|
||||
|
||||
int srcPos = 4;
|
||||
int destPos = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (srcPos >= srcSize)
|
||||
goto fail;
|
||||
|
||||
unsigned char flags = src[srcPos++];
|
||||
bool compressed = ((flags & 0x80) != 0);
|
||||
|
||||
if (compressed)
|
||||
{
|
||||
int length = (flags & 0x7F) + 3;
|
||||
unsigned char data = src[srcPos++];
|
||||
|
||||
if (destPos + length > destSize)
|
||||
goto fail;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
dest[destPos++] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = (flags & 0x7F) + 1;
|
||||
|
||||
if (destPos + length > destSize)
|
||||
goto fail;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
dest[destPos++] = src[srcPos++];
|
||||
}
|
||||
|
||||
if (destPos == destSize)
|
||||
{
|
||||
*uncompressedSize = destSize;
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
FATAL_ERROR("Fatal error while decompressing RL file.\n");
|
||||
}
|
||||
|
||||
unsigned char *RLCompress(unsigned char *src, int srcSize, int *compressedSize)
|
||||
{
|
||||
if (srcSize <= 0)
|
||||
goto fail;
|
||||
|
||||
int worstCaseDestSize = 4 + srcSize * 2;
|
||||
|
||||
// Round up to the next multiple of four.
|
||||
worstCaseDestSize = (worstCaseDestSize + 3) & ~3;
|
||||
|
||||
unsigned char *dest = malloc(worstCaseDestSize);
|
||||
|
||||
if (dest == NULL)
|
||||
goto fail;
|
||||
|
||||
// header
|
||||
dest[0] = 0x30; // RL compression type
|
||||
dest[1] = (unsigned char)srcSize;
|
||||
dest[2] = (unsigned char)(srcSize >> 8);
|
||||
dest[3] = (unsigned char)(srcSize >> 16);
|
||||
|
||||
int srcPos = 0;
|
||||
int destPos = 4;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
bool compress = false;
|
||||
int uncompressedStart = srcPos;
|
||||
int uncompressedLength = 0;
|
||||
|
||||
while (srcPos < srcSize && uncompressedLength < (0x7F + 1))
|
||||
{
|
||||
compress = (srcPos + 2 < srcSize && src[srcPos] == src[srcPos + 1] && src[srcPos] == src[srcPos + 2]);
|
||||
|
||||
if (compress)
|
||||
break;
|
||||
|
||||
srcPos++;
|
||||
uncompressedLength++;
|
||||
}
|
||||
|
||||
if (uncompressedLength > 0)
|
||||
{
|
||||
dest[destPos++] = uncompressedLength - 1;
|
||||
|
||||
for (int i = 0; i < uncompressedLength; i++)
|
||||
dest[destPos++] = src[uncompressedStart + i];
|
||||
}
|
||||
|
||||
if (compress)
|
||||
{
|
||||
unsigned char data = src[srcPos];
|
||||
int compressedLength = 0;
|
||||
|
||||
while (compressedLength < (0x7F + 3)
|
||||
&& srcPos + compressedLength < srcSize
|
||||
&& src[srcPos + compressedLength] == data)
|
||||
{
|
||||
compressedLength++;
|
||||
}
|
||||
|
||||
dest[destPos++] = 0x80 | (compressedLength - 3);
|
||||
dest[destPos++] = data;
|
||||
|
||||
srcPos += compressedLength;
|
||||
}
|
||||
|
||||
if (srcPos == srcSize)
|
||||
{
|
||||
// Pad to multiple of 4 bytes.
|
||||
int remainder = destPos % 4;
|
||||
|
||||
if (remainder != 0)
|
||||
{
|
||||
for (int i = 0; i < 4 - remainder; i++)
|
||||
dest[destPos++] = 0;
|
||||
}
|
||||
|
||||
*compressedSize = destPos;
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
FATAL_ERROR("Fatal error while compressing RL file.\n");
|
||||
}
|
9
tools/gbagfx/rl.h
Normal file
9
tools/gbagfx/rl.h
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright (c) 2016 YamaArashi
|
||||
|
||||
#ifndef RL_H
|
||||
#define RL_H
|
||||
|
||||
unsigned char *RLDecompress(unsigned char *src, int srcSize, int *uncompressedSize);
|
||||
unsigned char *RLCompress(unsigned char *src, int srcSize, int *compressedSize);
|
||||
|
||||
#endif // RL_H
|
124
tools/gbagfx/util.c
Normal file
124
tools/gbagfx/util.c
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include "global.h"
|
||||
#include "util.h"
|
||||
|
||||
bool ParseNumber(char *s, char **end, int radix, int *intValue)
|
||||
{
|
||||
char *localEnd;
|
||||
|
||||
if (end == NULL)
|
||||
end = &localEnd;
|
||||
|
||||
errno = 0;
|
||||
|
||||
const long longValue = strtol(s, end, radix);
|
||||
|
||||
if (*end == s)
|
||||
return false; // not a number
|
||||
|
||||
if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE)
|
||||
return false;
|
||||
|
||||
if (longValue > INT_MAX)
|
||||
return false;
|
||||
|
||||
if (longValue < INT_MIN)
|
||||
return false;
|
||||
|
||||
*intValue = (int)longValue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char *GetFileExtension(char *path)
|
||||
{
|
||||
char *extension = path;
|
||||
|
||||
while (*extension != 0)
|
||||
extension++;
|
||||
|
||||
while (extension > path && *extension != '.')
|
||||
extension--;
|
||||
|
||||
if (extension == path)
|
||||
return NULL;
|
||||
|
||||
extension++;
|
||||
|
||||
if (*extension == 0)
|
||||
return NULL;
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
unsigned char *ReadWholeFile(char *path, int *size)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
|
||||
*size = ftell(fp);
|
||||
|
||||
unsigned char *buffer = malloc(*size);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
|
||||
|
||||
rewind(fp);
|
||||
|
||||
if (fread(buffer, *size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
|
||||
*size = ftell(fp);
|
||||
|
||||
unsigned char *buffer = calloc(*size + padAmount, 1);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
|
||||
|
||||
rewind(fp);
|
||||
|
||||
if (fread(buffer, *size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void WriteWholeFile(char *path, void *buffer, int bufferSize)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
|
||||
|
||||
if (fwrite(buffer, bufferSize, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to write to \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
}
|
14
tools/gbagfx/util.h
Normal file
14
tools/gbagfx/util.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool ParseNumber(char *s, char **end, int radix, int *intValue);
|
||||
char *GetFileExtension(char *path);
|
||||
unsigned char *ReadWholeFile(char *path, int *size);
|
||||
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount);
|
||||
void WriteWholeFile(char *path, void *buffer, int bufferSize);
|
||||
|
||||
#endif // UTIL_H
|
1
tools/mid2agb/.gitignore
vendored
Normal file
1
tools/mid2agb/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
mid2agb
|
19
tools/mid2agb/LICENSE
Normal file
19
tools/mid2agb/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 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.
|
15
tools/mid2agb/Makefile
Normal file
15
tools/mid2agb/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CXX := g++
|
||||
|
||||
CXXFLAGS := -std=c++11 -O2 -s -Wall -Wno-switch -Werror
|
||||
|
||||
SRCS := agb.cpp error.cpp main.cpp midi.cpp tables.cpp
|
||||
|
||||
HEADERS := agb.h error.h main.h midi.h tables.h
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
mid2agb: $(SRCS) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) mid2agb mid2agb.exe
|
462
tools/mid2agb/agb.cpp
Normal file
462
tools/mid2agb/agb.cpp
Normal file
@ -0,0 +1,462 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include "agb.h"
|
||||
#include "main.h"
|
||||
#include "midi.h"
|
||||
#include "tables.h"
|
||||
|
||||
int g_agbTrack;
|
||||
|
||||
static std::string s_lastOpName;
|
||||
static int s_blockNum;
|
||||
static bool s_keepLastOpName;
|
||||
static int s_lastNote;
|
||||
static int s_lastVelocity;
|
||||
static bool s_noteChanged;
|
||||
static bool s_velocityChanged;
|
||||
static bool s_inPattern;
|
||||
static int s_extendedCommand;
|
||||
|
||||
void PrintAgbHeader()
|
||||
{
|
||||
std::fprintf(g_outputFile, "\t.include \"MPlayDef.s\"\n\n");
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_grp, voicegroup%03u\n", g_asmLabel.c_str(), g_voiceGroup);
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_pri, %u\n", g_asmLabel.c_str(), g_priority);
|
||||
|
||||
if (g_reverb >= 0)
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_rev, reverb_set+%u\n", g_asmLabel.c_str(), g_reverb);
|
||||
else
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_rev, 0\n", g_asmLabel.c_str());
|
||||
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_mvl, %u\n", g_asmLabel.c_str(), g_masterVolume);
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_key, %u\n", g_asmLabel.c_str(), 0);
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_tbs, %u\n", g_asmLabel.c_str(), g_clocksPerBeat);
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_exg, %u\n", g_asmLabel.c_str(), g_exactGateTime);
|
||||
std::fprintf(g_outputFile, "\t.equ\t%s_cmp, %u\n", g_asmLabel.c_str(), g_compressionEnabled);
|
||||
|
||||
std::fprintf(g_outputFile, "\n\t.section .rodata\n");
|
||||
std::fprintf(g_outputFile, "\t.global\t%s\n", g_asmLabel.c_str());
|
||||
|
||||
std::fprintf(g_outputFile, "\t.align\t2\n");
|
||||
}
|
||||
|
||||
void ResetTrackVars()
|
||||
{
|
||||
s_lastVelocity = -1;
|
||||
s_lastNote = -1;
|
||||
s_velocityChanged = false;
|
||||
s_noteChanged = false;
|
||||
s_keepLastOpName = false;
|
||||
s_lastOpName = "";
|
||||
s_inPattern = false;
|
||||
}
|
||||
|
||||
void PrintWait(int wait)
|
||||
{
|
||||
if (wait > 0)
|
||||
{
|
||||
std::fprintf(g_outputFile, "\t.byte\tW%02d\n", wait);
|
||||
s_velocityChanged = true;
|
||||
s_noteChanged = true;
|
||||
s_keepLastOpName = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintOp(int wait, std::string name, const char *format, ...)
|
||||
{
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
std::fprintf(g_outputFile, "\t.byte\t\t");
|
||||
|
||||
if (format != nullptr)
|
||||
{
|
||||
if (!g_compressionEnabled || s_lastOpName != name)
|
||||
{
|
||||
std::fprintf(g_outputFile, "%s, ", name.c_str());
|
||||
s_lastOpName = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fprintf(g_outputFile, " ");
|
||||
}
|
||||
std::vfprintf(g_outputFile, format, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::fputs(name.c_str(), g_outputFile);
|
||||
s_lastOpName = name;
|
||||
}
|
||||
|
||||
std::fprintf(g_outputFile, "\n");
|
||||
|
||||
va_end(args);
|
||||
|
||||
PrintWait(wait);
|
||||
}
|
||||
|
||||
void PrintByte(const char *format, ...)
|
||||
{
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
std::fprintf(g_outputFile, "\t.byte\t");
|
||||
std::vfprintf(g_outputFile, format, args);
|
||||
std::fprintf(g_outputFile, "\n");
|
||||
s_velocityChanged = true;
|
||||
s_noteChanged = true;
|
||||
s_keepLastOpName = true;
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void PrintWord(const char *format, ...)
|
||||
{
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
std::fprintf(g_outputFile, "\t .word\t");
|
||||
std::vfprintf(g_outputFile, format, args);
|
||||
std::fprintf(g_outputFile, "\n");
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void PrintNote(const Event& event)
|
||||
{
|
||||
int note = event.note;
|
||||
int velocity = g_noteVelocityLUT[event.param1];
|
||||
int duration = -1;
|
||||
|
||||
if (event.param2 != -1)
|
||||
duration = g_noteDurationLUT[event.param2];
|
||||
|
||||
int gateTimeParam = 0;
|
||||
|
||||
if (g_exactGateTime && duration != -1)
|
||||
gateTimeParam = event.param2 - duration;
|
||||
|
||||
char gtpBuf[16];
|
||||
|
||||
if (gateTimeParam > 0)
|
||||
std::snprintf(gtpBuf, sizeof(gtpBuf), ", gtp%u", gateTimeParam);
|
||||
else
|
||||
gtpBuf[0] = 0;
|
||||
|
||||
char opName[16];
|
||||
|
||||
if (duration == -1)
|
||||
std::strcpy(opName, "TIE ");
|
||||
else
|
||||
std::snprintf(opName, sizeof(opName), "N%02u ", duration);
|
||||
|
||||
bool noteChanged = true;
|
||||
bool velocityChanged = true;
|
||||
|
||||
if (g_compressionEnabled)
|
||||
{
|
||||
noteChanged = (note != s_lastNote);
|
||||
velocityChanged = (velocity != s_lastVelocity);
|
||||
}
|
||||
|
||||
if (s_keepLastOpName)
|
||||
s_keepLastOpName = false;
|
||||
else
|
||||
s_lastOpName = "";
|
||||
|
||||
if (noteChanged || velocityChanged || (gateTimeParam > 0))
|
||||
{
|
||||
s_lastNote = note;
|
||||
|
||||
char noteBuf[16];
|
||||
|
||||
if (note >= 24)
|
||||
std::snprintf(noteBuf, sizeof(noteBuf), g_noteTable[note % 12], note / 12 - 2);
|
||||
else
|
||||
std::snprintf(noteBuf, sizeof(noteBuf), g_minusNoteTable[note % 12], note / -12 + 2);
|
||||
|
||||
char velocityBuf[16];
|
||||
|
||||
if (velocityChanged || (gateTimeParam > 0))
|
||||
{
|
||||
s_lastVelocity = velocity;
|
||||
std::snprintf(velocityBuf, sizeof(velocityBuf), ", v%03u", velocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
velocityBuf[0] = 0;
|
||||
}
|
||||
|
||||
PrintOp(event.time, opName, "%s%s%s", noteBuf, velocityBuf, gtpBuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintOp(event.time, opName, 0);
|
||||
}
|
||||
|
||||
s_noteChanged = noteChanged;
|
||||
s_velocityChanged = velocityChanged;
|
||||
}
|
||||
|
||||
void PrintEndOfTieOp(const Event& event)
|
||||
{
|
||||
int note = event.note;
|
||||
bool noteChanged = (note != s_lastNote);
|
||||
|
||||
if (!noteChanged || !s_noteChanged)
|
||||
s_lastOpName = "";
|
||||
|
||||
if (!noteChanged && g_compressionEnabled)
|
||||
{
|
||||
PrintOp(event.time, "EOT ", nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_lastNote = note;
|
||||
if (note >= 24)
|
||||
PrintOp(event.time, "EOT ", g_noteTable[note % 12], note / 12 - 2);
|
||||
else
|
||||
PrintOp(event.time, "EOT ", g_minusNoteTable[note % 12], note / -12 + 2);
|
||||
}
|
||||
|
||||
s_noteChanged = noteChanged;
|
||||
}
|
||||
|
||||
void PrintSeqLoopLabel(const Event& event)
|
||||
{
|
||||
s_blockNum = event.param1 + 1;
|
||||
std::fprintf(g_outputFile, "%s_%u_B%u:\n", g_asmLabel.c_str(), g_agbTrack, s_blockNum);
|
||||
PrintWait(event.time);
|
||||
ResetTrackVars();
|
||||
}
|
||||
|
||||
void PrintExtendedOp(const Event& event)
|
||||
{
|
||||
// TODO: support for other extended commands
|
||||
|
||||
switch (s_extendedCommand)
|
||||
{
|
||||
case 0x08:
|
||||
PrintOp(event.time, "XCMD ", "xIECV , %u", event.param2);
|
||||
break;
|
||||
case 0x09:
|
||||
PrintOp(event.time, "XCMD ", "xIECL , %u", event.param2);
|
||||
break;
|
||||
default:
|
||||
PrintWait(event.time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintControllerOp(const Event& event)
|
||||
{
|
||||
switch (event.param1)
|
||||
{
|
||||
case 0x01:
|
||||
PrintOp(event.time, "MOD ", "%u", event.param2);
|
||||
break;
|
||||
case 0x07:
|
||||
PrintOp(event.time, "VOL ", "%u*%s_mvl/mxv", event.param2, g_asmLabel.c_str());
|
||||
break;
|
||||
case 0x0A:
|
||||
PrintOp(event.time, "PAN ", "c_v%+d", event.param2 - 64);
|
||||
break;
|
||||
case 0x0C:
|
||||
case 0x10:
|
||||
// TODO: memacc
|
||||
break;
|
||||
case 0x0D:
|
||||
// TODO: memacc var
|
||||
break;
|
||||
case 0x0E:
|
||||
// TODO: memacc var
|
||||
break;
|
||||
case 0x0F:
|
||||
// TODO: memacc var
|
||||
break;
|
||||
case 0x11:
|
||||
std::fprintf(g_outputFile, "%s_%u_L%u:\n", g_asmLabel.c_str(), g_agbTrack, event.param2);
|
||||
PrintWait(event.time);
|
||||
ResetTrackVars();
|
||||
break;
|
||||
case 0x14:
|
||||
PrintOp(event.time, "BENDR ", "%u", event.param2);
|
||||
break;
|
||||
case 0x15:
|
||||
PrintOp(event.time, "LFOS ", "%u", event.param2);
|
||||
break;
|
||||
case 0x16:
|
||||
PrintOp(event.time, "MODT ", "%u", event.param2);
|
||||
break;
|
||||
case 0x18:
|
||||
PrintOp(event.time, "TUNE ", "c_v%+d", event.param2 - 64);
|
||||
break;
|
||||
case 0x1A:
|
||||
PrintOp(event.time, "LFODL ", "%u", event.param2);
|
||||
break;
|
||||
case 0x1D:
|
||||
case 0x1F:
|
||||
PrintExtendedOp(event);
|
||||
break;
|
||||
case 0x1E:
|
||||
s_extendedCommand = event.param2;
|
||||
// TODO: loop op
|
||||
break;
|
||||
case 0x21:
|
||||
case 0x27:
|
||||
PrintByte("PRIO , %u", event.param2);
|
||||
PrintWait(event.time);
|
||||
break;
|
||||
default:
|
||||
PrintWait(event.time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintAgbTrack(std::vector<Event>& events)
|
||||
{
|
||||
std::fprintf(g_outputFile, "\n@**************** Track %u (Midi-Chn.%u) ****************@\n\n", g_agbTrack, g_midiChan + 1);
|
||||
std::fprintf(g_outputFile, "%s_%u:\n", g_asmLabel.c_str(), g_agbTrack);
|
||||
PrintWait(g_initialWait);
|
||||
PrintByte("KEYSH , %s_key%+d", g_asmLabel.c_str(), 0);
|
||||
|
||||
int wholeNoteCount = 0;
|
||||
int loopEndBlockNum = 0;
|
||||
|
||||
ResetTrackVars();
|
||||
|
||||
bool foundVolBeforeNote = false;
|
||||
|
||||
for (const Event& event : events)
|
||||
{
|
||||
if (event.type == EventType::Note)
|
||||
break;
|
||||
|
||||
if (event.type == EventType::Controller && event.param1 == 0x07)
|
||||
{
|
||||
foundVolBeforeNote = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundVolBeforeNote)
|
||||
PrintByte("\tVOL , 127*%s_mvl/mxv", g_asmLabel.c_str());
|
||||
|
||||
for (unsigned i = 0; events[i].type != EventType::EndOfTrack; i++)
|
||||
{
|
||||
const Event& event = events[i];
|
||||
|
||||
if (IsPatternBoundary(event.type))
|
||||
{
|
||||
if (s_inPattern)
|
||||
PrintByte("PEND");
|
||||
s_inPattern = false;
|
||||
}
|
||||
|
||||
if (event.type == EventType::WholeNoteMark || event.type == EventType::Pattern)
|
||||
std::fprintf(g_outputFile, "@ %03d ----------------------------------------\n", wholeNoteCount++);
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case EventType::Note:
|
||||
PrintNote(event);
|
||||
break;
|
||||
case EventType::EndOfTie:
|
||||
PrintEndOfTieOp(event);
|
||||
break;
|
||||
case EventType::Label:
|
||||
PrintSeqLoopLabel(event);
|
||||
break;
|
||||
case EventType::LoopEnd:
|
||||
PrintByte("GOTO");
|
||||
PrintWord("%s_%u_B%u", g_asmLabel.c_str(), g_agbTrack, loopEndBlockNum);
|
||||
PrintSeqLoopLabel(event);
|
||||
break;
|
||||
case EventType::LoopEndBegin:
|
||||
PrintByte("GOTO");
|
||||
PrintWord("%s_%u_B%u", g_asmLabel.c_str(), g_agbTrack, loopEndBlockNum);
|
||||
PrintSeqLoopLabel(event);
|
||||
loopEndBlockNum = s_blockNum;
|
||||
break;
|
||||
case EventType::LoopBegin:
|
||||
PrintSeqLoopLabel(event);
|
||||
loopEndBlockNum = s_blockNum;
|
||||
break;
|
||||
case EventType::WholeNoteMark:
|
||||
if (event.param2 & 0x80000000)
|
||||
{
|
||||
std::fprintf(g_outputFile, "%s_%u_%03lu:\n", g_asmLabel.c_str(), g_agbTrack, (unsigned long)(event.param2 & 0x7FFFFFFF));
|
||||
ResetTrackVars();
|
||||
s_inPattern = true;
|
||||
}
|
||||
PrintWait(event.time);
|
||||
break;
|
||||
case EventType::Pattern:
|
||||
PrintByte("PATT");
|
||||
PrintWord("%s_%u_%03lu", g_asmLabel.c_str(), g_agbTrack, event.param2);
|
||||
|
||||
while (!IsPatternBoundary(events[i + 1].type))
|
||||
i++;
|
||||
|
||||
ResetTrackVars();
|
||||
break;
|
||||
case EventType::Tempo:
|
||||
PrintByte("TEMPO , %u*%s_tbs/2", 60000000 / event.param2, g_asmLabel.c_str());
|
||||
PrintWait(event.time);
|
||||
break;
|
||||
case EventType::InstrumentChange:
|
||||
PrintOp(event.time, "VOICE ", "%u", event.param1);
|
||||
break;
|
||||
case EventType::PitchBend:
|
||||
PrintOp(event.time, "BEND ", "c_v%+d", event.param2 - 64);
|
||||
break;
|
||||
case EventType::Controller:
|
||||
PrintControllerOp(event);
|
||||
break;
|
||||
default:
|
||||
PrintWait(event.time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PrintByte("FINE");
|
||||
}
|
||||
|
||||
void PrintAgbFooter()
|
||||
{
|
||||
int trackCount = g_agbTrack - 1;
|
||||
|
||||
std::fprintf(g_outputFile, "\n@******************************************************@\n");
|
||||
std::fprintf(g_outputFile, "\t.align\t2\n");
|
||||
std::fprintf(g_outputFile, "\n%s:\n", g_asmLabel.c_str());
|
||||
std::fprintf(g_outputFile, "\t.byte\t%u\t@ NumTrks\n", trackCount);
|
||||
std::fprintf(g_outputFile, "\t.byte\t%u\t@ NumBlks\n", 0);
|
||||
std::fprintf(g_outputFile, "\t.byte\t%s_pri\t@ Priority\n", g_asmLabel.c_str());
|
||||
std::fprintf(g_outputFile, "\t.byte\t%s_rev\t@ Reverb.\n", g_asmLabel.c_str());
|
||||
std::fprintf(g_outputFile, "\n");
|
||||
std::fprintf(g_outputFile, "\t.word\t%s_grp\n", g_asmLabel.c_str());
|
||||
std::fprintf(g_outputFile, "\n");
|
||||
|
||||
// track pointers
|
||||
for (int i = 1; i <= trackCount; i++)
|
||||
std::fprintf(g_outputFile, "\t.word\t%s_%u\n", g_asmLabel.c_str(), i);
|
||||
|
||||
std::fprintf(g_outputFile, "\n\t.end\n");
|
||||
}
|
33
tools/mid2agb/agb.h
Normal file
33
tools/mid2agb/agb.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright(c) 2016 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 AGB_H
|
||||
#define AGB_H
|
||||
|
||||
#include <vector>
|
||||
#include "midi.h"
|
||||
|
||||
void PrintAgbHeader();
|
||||
void PrintAgbTrack(std::vector<Event>& events);
|
||||
void PrintAgbFooter();
|
||||
|
||||
extern int g_agbTrack;
|
||||
|
||||
#endif // AGB_H
|
36
tools/mid2agb/error.cpp
Normal file
36
tools/mid2agb/error.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
|
||||
// Reports an error diagnostic and terminates the program.
|
||||
[[noreturn]] void RaiseError(const char* format, ...)
|
||||
{
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
std::vsnprintf(buffer, bufferSize, format, args);
|
||||
std::fprintf(stderr, "error: %s\n", buffer);
|
||||
va_end(args);
|
||||
std::exit(1);
|
||||
}
|
26
tools/mid2agb/error.h
Normal file
26
tools/mid2agb/error.h
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright(c) 2016 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 ERROR_H
|
||||
#define ERROR_H
|
||||
|
||||
[[noreturn]] void RaiseError(const char* format, ...);
|
||||
|
||||
#endif // ERROR_H
|
230
tools/mid2agb/main.cpp
Normal file
230
tools/mid2agb/main.cpp
Normal file
@ -0,0 +1,230 @@
|
||||
// Copyright(c) 2016 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 <cstring>
|
||||
#include <cctype>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "main.h"
|
||||
#include "error.h"
|
||||
#include "midi.h"
|
||||
#include "agb.h"
|
||||
|
||||
FILE* g_inputFile = nullptr;
|
||||
FILE* g_outputFile = nullptr;
|
||||
|
||||
std::string g_asmLabel;
|
||||
int g_masterVolume = 127;
|
||||
int g_voiceGroup = 0;
|
||||
int g_priority = 0;
|
||||
int g_reverb = -1;
|
||||
int g_clocksPerBeat = 1;
|
||||
bool g_exactGateTime = false;
|
||||
bool g_compressionEnabled = true;
|
||||
|
||||
[[noreturn]] static void PrintUsage()
|
||||
{
|
||||
std::printf(
|
||||
"Usage: MID2AGB name [options]\n"
|
||||
"\n"
|
||||
" input_file filename(.mid) of MIDI file\n"
|
||||
" output_file filename(.s) for AGB file (default:input_file)\n"
|
||||
"\n"
|
||||
"options -V??? master volume (default:127)\n"
|
||||
" -G??? voice group number (default:0)\n"
|
||||
" -P??? priority (default:0)\n"
|
||||
" -R??? reverb (default:off)\n"
|
||||
" -X 48 clocks/beat (default:24 clocks/beat)\n"
|
||||
" -E exact gate-time\n"
|
||||
" -N no compression\n"
|
||||
);
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
static std::string StripExtension(std::string s)
|
||||
{
|
||||
std::size_t pos = s.find_last_of('.');
|
||||
|
||||
if (pos > 0 && pos != std::string::npos)
|
||||
{
|
||||
s = s.substr(0, pos);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static std::string GetExtension(std::string s)
|
||||
{
|
||||
std::size_t pos = s.find_last_of('.');
|
||||
|
||||
if (pos > 0 && pos != std::string::npos)
|
||||
{
|
||||
return s.substr(pos + 1);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
struct Option
|
||||
{
|
||||
char letter = 0;
|
||||
const char *arg = NULL;
|
||||
};
|
||||
|
||||
static Option ParseOption(int& index, const int argc, char** argv)
|
||||
{
|
||||
static std::set<char> optionsWithArg = { 'L', 'V', 'G', 'P', 'R' };
|
||||
static std::set<char> optionsWithoutArg = { 'X', 'E', 'N' };
|
||||
|
||||
assert(index >= 0 && index < argc);
|
||||
|
||||
const char *opt = argv[index];
|
||||
|
||||
assert(opt[0] == '-');
|
||||
assert(std::strlen(opt) == 2);
|
||||
|
||||
char letter = std::toupper(opt[1]);
|
||||
|
||||
bool isOption = false;
|
||||
bool hasArg = false;
|
||||
|
||||
if (optionsWithArg.count(letter) != 0)
|
||||
{
|
||||
isOption = true;
|
||||
hasArg = true;
|
||||
}
|
||||
else if (optionsWithoutArg.count(letter) != 0)
|
||||
{
|
||||
isOption = true;
|
||||
}
|
||||
|
||||
if (!isOption)
|
||||
PrintUsage();
|
||||
|
||||
Option retVal;
|
||||
|
||||
retVal.letter = letter;
|
||||
|
||||
if (hasArg)
|
||||
{
|
||||
index++;
|
||||
|
||||
if (index >= argc)
|
||||
RaiseError("missing argument for \"-%c\"", letter);
|
||||
|
||||
retVal.arg = argv[index];
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::string inputFilename;
|
||||
std::string outputFilename;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (argv[i][0] == '-' && std::strlen(argv[i]) == 2)
|
||||
{
|
||||
Option option = ParseOption(i, argc, argv);
|
||||
|
||||
switch (option.letter)
|
||||
{
|
||||
case 'E':
|
||||
g_exactGateTime = true;
|
||||
break;
|
||||
case 'G':
|
||||
g_voiceGroup = std::stoi(option.arg);
|
||||
break;
|
||||
case 'L':
|
||||
g_asmLabel = option.arg;
|
||||
break;
|
||||
case 'N':
|
||||
g_compressionEnabled = false;
|
||||
break;
|
||||
case 'P':
|
||||
g_priority = std::stoi(option.arg);
|
||||
break;
|
||||
case 'R':
|
||||
g_reverb = std::stoi(option.arg);
|
||||
break;
|
||||
case 'V':
|
||||
g_masterVolume = std::stoi(option.arg);
|
||||
break;
|
||||
case 'X':
|
||||
g_clocksPerBeat = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
inputFilename = argv[i];
|
||||
break;
|
||||
case 2:
|
||||
outputFilename = argv[i];
|
||||
break;
|
||||
default:
|
||||
PrintUsage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputFilename.empty())
|
||||
PrintUsage();
|
||||
|
||||
if (GetExtension(inputFilename) != "mid")
|
||||
RaiseError("input filename extension is not \"mid\"");
|
||||
|
||||
if (outputFilename.empty())
|
||||
outputFilename = StripExtension(inputFilename) + ".s";
|
||||
|
||||
if (GetExtension(outputFilename) != "s")
|
||||
RaiseError("output filename extension is not \"s\"");
|
||||
|
||||
if (g_asmLabel.empty())
|
||||
g_asmLabel = StripExtension(outputFilename);
|
||||
|
||||
g_inputFile = std::fopen(inputFilename.c_str(), "rb");
|
||||
|
||||
if (g_inputFile == nullptr)
|
||||
RaiseError("failed to open \"%s\" for reading", inputFilename.c_str());
|
||||
|
||||
g_outputFile = std::fopen(outputFilename.c_str(), "w");
|
||||
|
||||
if (g_outputFile == nullptr)
|
||||
RaiseError("failed to open \"%s\" for writing", outputFilename.c_str());
|
||||
|
||||
ReadMidiFileHeader();
|
||||
PrintAgbHeader();
|
||||
ReadMidiTracks();
|
||||
PrintAgbFooter();
|
||||
|
||||
std::fclose(g_inputFile);
|
||||
std::fclose(g_outputFile);
|
||||
|
||||
return 0;
|
||||
}
|
39
tools/mid2agb/main.h
Normal file
39
tools/mid2agb/main.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright(c) 2016 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 MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
extern FILE* g_inputFile;
|
||||
extern FILE* g_outputFile;
|
||||
|
||||
extern std::string g_asmLabel;
|
||||
extern int g_masterVolume;
|
||||
extern int g_voiceGroup;
|
||||
extern int g_priority;
|
||||
extern int g_reverb;
|
||||
extern int g_clocksPerBeat;
|
||||
extern bool g_exactGateTime;
|
||||
extern bool g_compressionEnabled;
|
||||
|
||||
#endif // MAIN_H
|
942
tools/mid2agb/midi.cpp
Normal file
942
tools/mid2agb/midi.cpp
Normal file
@ -0,0 +1,942 @@
|
||||
// Copyright(c) 2016 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 <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "midi.h"
|
||||
#include "main.h"
|
||||
#include "error.h"
|
||||
#include "agb.h"
|
||||
#include "tables.h"
|
||||
|
||||
enum class MidiEventCategory
|
||||
{
|
||||
Control,
|
||||
SysEx,
|
||||
Meta,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
MidiFormat g_midiFormat;
|
||||
std::int_fast32_t g_midiTrackCount;
|
||||
std::int16_t g_midiTimeDiv;
|
||||
|
||||
int g_midiChan;
|
||||
std::int32_t g_initialWait;
|
||||
|
||||
static long s_trackDataStart;
|
||||
static std::vector<Event> s_seqEvents;
|
||||
static std::vector<Event> s_trackEvents;
|
||||
static std::int32_t s_absoluteTime;
|
||||
static int s_blockCount = 0;
|
||||
static int s_minNote;
|
||||
static int s_maxNote;
|
||||
|
||||
void Seek(long offset)
|
||||
{
|
||||
if (std::fseek(g_inputFile, offset, SEEK_SET) != 0)
|
||||
RaiseError("failed to seek to %l", offset);
|
||||
}
|
||||
|
||||
void Skip(long offset)
|
||||
{
|
||||
if (std::fseek(g_inputFile, offset, SEEK_CUR) != 0)
|
||||
RaiseError("failed to skip %l bytes", offset);
|
||||
}
|
||||
|
||||
std::string ReadSignature()
|
||||
{
|
||||
char signature[4];
|
||||
|
||||
if (std::fread(signature, 4, 1, g_inputFile) != 1)
|
||||
RaiseError("failed to read signature");
|
||||
|
||||
return std::string(signature, 4);
|
||||
}
|
||||
|
||||
std::uint32_t ReadInt8()
|
||||
{
|
||||
int c = std::fgetc(g_inputFile);
|
||||
|
||||
if (c < 0)
|
||||
RaiseError("unexpected EOF");
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
std::uint32_t ReadInt16()
|
||||
{
|
||||
std::uint32_t val = 0;
|
||||
val |= ReadInt8() << 8;
|
||||
val |= ReadInt8();
|
||||
return val;
|
||||
}
|
||||
|
||||
std::uint32_t ReadInt24()
|
||||
{
|
||||
std::uint32_t val = 0;
|
||||
val |= ReadInt8() << 16;
|
||||
val |= ReadInt8() << 8;
|
||||
val |= ReadInt8();
|
||||
return val;
|
||||
}
|
||||
|
||||
std::uint32_t ReadInt32()
|
||||
{
|
||||
std::uint32_t val = 0;
|
||||
val |= ReadInt8() << 24;
|
||||
val |= ReadInt8() << 16;
|
||||
val |= ReadInt8() << 8;
|
||||
val |= ReadInt8();
|
||||
return val;
|
||||
}
|
||||
|
||||
std::uint32_t ReadVLQ()
|
||||
{
|
||||
std::uint32_t val = 0;
|
||||
std::uint32_t c;
|
||||
|
||||
do
|
||||
{
|
||||
c = ReadInt8();
|
||||
val <<= 7;
|
||||
val |= (c & 0x7F);
|
||||
} while (c & 0x80);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
void ReadMidiFileHeader()
|
||||
{
|
||||
Seek(0);
|
||||
|
||||
if (ReadSignature() != "MThd")
|
||||
RaiseError("MIDI file header signature didn't match \"MThd\"");
|
||||
|
||||
std::uint32_t headerLength = ReadInt32();
|
||||
|
||||
if (headerLength != 6)
|
||||
RaiseError("MIDI file header length isn't 6");
|
||||
|
||||
std::uint16_t midiFormat = ReadInt16();
|
||||
|
||||
if (midiFormat >= 2)
|
||||
RaiseError("unsupported MIDI format (%u)", midiFormat);
|
||||
|
||||
g_midiFormat = (MidiFormat)midiFormat;
|
||||
g_midiTrackCount = ReadInt16();
|
||||
g_midiTimeDiv = ReadInt16();
|
||||
|
||||
if (g_midiTimeDiv < 0)
|
||||
RaiseError("unsupported MIDI time division (%d)", g_midiTimeDiv);
|
||||
}
|
||||
|
||||
long ReadMidiTrackHeader(long offset)
|
||||
{
|
||||
Seek(offset);
|
||||
|
||||
if (ReadSignature() != "MTrk")
|
||||
RaiseError("MIDI track header signature didn't match \"MTrk\"");
|
||||
|
||||
long size = ReadInt32();
|
||||
|
||||
s_trackDataStart = std::ftell(g_inputFile);
|
||||
|
||||
return size + 8;
|
||||
}
|
||||
|
||||
void StartTrack()
|
||||
{
|
||||
Seek(s_trackDataStart);
|
||||
s_absoluteTime = 0;
|
||||
}
|
||||
|
||||
void SkipEventData()
|
||||
{
|
||||
Skip(ReadVLQ());
|
||||
}
|
||||
|
||||
void DetermineEventCategory(MidiEventCategory& category, int& typeChan, int& size)
|
||||
{
|
||||
typeChan = ReadInt8();
|
||||
|
||||
if (typeChan == 0xFF)
|
||||
{
|
||||
category = MidiEventCategory::Meta;
|
||||
size = 0;
|
||||
}
|
||||
else if (typeChan >= 0xF0)
|
||||
{
|
||||
category = MidiEventCategory::SysEx;
|
||||
size = 0;
|
||||
}
|
||||
else if (typeChan >= 0x80)
|
||||
{
|
||||
category = MidiEventCategory::Control;
|
||||
|
||||
switch (typeChan >> 4)
|
||||
{
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
size = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
category = MidiEventCategory::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
void MakeBlockEvent(Event& event, EventType type)
|
||||
{
|
||||
event.type = type;
|
||||
event.param1 = s_blockCount++;
|
||||
event.param2 = 0;
|
||||
}
|
||||
|
||||
std::string ReadEventText()
|
||||
{
|
||||
char buffer[2];
|
||||
std::uint32_t length = ReadVLQ();
|
||||
|
||||
if (length <= 2)
|
||||
{
|
||||
if (fread(buffer, length, 1, g_inputFile) != 1)
|
||||
RaiseError("failed to read event text");
|
||||
}
|
||||
else
|
||||
{
|
||||
Skip(length);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
return std::string(buffer, length);
|
||||
}
|
||||
|
||||
bool ReadSeqEvent(Event& event)
|
||||
{
|
||||
s_absoluteTime += ReadVLQ();
|
||||
event.time = s_absoluteTime;
|
||||
|
||||
MidiEventCategory category;
|
||||
int typeChan;
|
||||
int size;
|
||||
|
||||
DetermineEventCategory(category, typeChan, size);
|
||||
|
||||
if (category == MidiEventCategory::Control)
|
||||
{
|
||||
Skip(size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (category == MidiEventCategory::SysEx)
|
||||
{
|
||||
SkipEventData();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (category == MidiEventCategory::Invalid)
|
||||
RaiseError("invalid event");
|
||||
|
||||
// meta event
|
||||
int metaEventType = ReadInt8();
|
||||
|
||||
if (metaEventType >= 1 && metaEventType <= 7)
|
||||
{
|
||||
// text event
|
||||
std::string text = ReadEventText();
|
||||
|
||||
if (text == "[")
|
||||
MakeBlockEvent(event, EventType::LoopBegin);
|
||||
else if (text == "][")
|
||||
MakeBlockEvent(event, EventType::LoopEndBegin);
|
||||
else if (text == "]")
|
||||
MakeBlockEvent(event, EventType::LoopEnd);
|
||||
else if (text == ":")
|
||||
MakeBlockEvent(event, EventType::Label);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (metaEventType)
|
||||
{
|
||||
case 0x2F: // end of track
|
||||
SkipEventData();
|
||||
event.type = EventType::EndOfTrack;
|
||||
event.param1 = 0;
|
||||
event.param2 = 0;
|
||||
break;
|
||||
case 0x51: // tempo
|
||||
if (ReadVLQ() != 3)
|
||||
RaiseError("invalid tempo size");
|
||||
|
||||
event.type = EventType::Tempo;
|
||||
event.param1 = 0;
|
||||
event.param2 = ReadInt24();
|
||||
break;
|
||||
case 0x58: // time signature
|
||||
{
|
||||
if (ReadVLQ() != 4)
|
||||
RaiseError("invalid time signature size");
|
||||
|
||||
int numerator = ReadInt8();
|
||||
int denominatorExponent = ReadInt8();
|
||||
|
||||
if (denominatorExponent >= 16)
|
||||
RaiseError("invalid time signature denominator");
|
||||
|
||||
Skip(2); // ignore other values
|
||||
|
||||
int clockTicks = 96 * numerator * g_clocksPerBeat;
|
||||
int denominator = 1 << denominatorExponent;
|
||||
int timeSig = clockTicks / denominator;
|
||||
|
||||
if (timeSig <= 0 || timeSig >= 0x10000)
|
||||
RaiseError("invalid time signature");
|
||||
|
||||
event.type = EventType::TimeSignature;
|
||||
event.param1 = 0;
|
||||
event.param2 = timeSig;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SkipEventData();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadSeqEvents()
|
||||
{
|
||||
StartTrack();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Event event = {};
|
||||
|
||||
if (ReadSeqEvent(event))
|
||||
{
|
||||
s_seqEvents.push_back(event);
|
||||
|
||||
if (event.type == EventType::EndOfTrack)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckNoteEnd(Event& event)
|
||||
{
|
||||
event.param2 += ReadVLQ();
|
||||
|
||||
MidiEventCategory category;
|
||||
int typeChan;
|
||||
int size;
|
||||
|
||||
DetermineEventCategory(category, typeChan, size);
|
||||
|
||||
if (category == MidiEventCategory::Control)
|
||||
{
|
||||
int chan = typeChan & 0xF;
|
||||
|
||||
if (chan != g_midiChan)
|
||||
{
|
||||
Skip(size);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (typeChan & 0xF0)
|
||||
{
|
||||
case 0x80: // note off
|
||||
{
|
||||
int note = ReadInt8();
|
||||
ReadInt8(); // ignore velocity
|
||||
if (note == event.note)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
case 0x90: // note on
|
||||
{
|
||||
int note = ReadInt8();
|
||||
int velocity = ReadInt8();
|
||||
if (velocity == 0 && note == event.note)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Skip(size);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (category == MidiEventCategory::SysEx)
|
||||
{
|
||||
SkipEventData();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (category == MidiEventCategory::Meta)
|
||||
{
|
||||
int metaEventType = ReadInt8();
|
||||
SkipEventData();
|
||||
|
||||
if (metaEventType == 0x2F)
|
||||
RaiseError("note doesn't end");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RaiseError("invalid event");
|
||||
}
|
||||
|
||||
void FindNoteEnd(Event& event)
|
||||
{
|
||||
long startPos = ftell(g_inputFile);
|
||||
|
||||
event.param2 = 0;
|
||||
|
||||
while (!CheckNoteEnd(event))
|
||||
;
|
||||
|
||||
Seek(startPos);
|
||||
}
|
||||
|
||||
bool ReadTrackEvent(Event& event)
|
||||
{
|
||||
s_absoluteTime += ReadVLQ();
|
||||
event.time = s_absoluteTime;
|
||||
|
||||
MidiEventCategory category;
|
||||
int typeChan;
|
||||
int size;
|
||||
|
||||
DetermineEventCategory(category, typeChan, size);
|
||||
|
||||
if (category == MidiEventCategory::Control)
|
||||
{
|
||||
int chan = typeChan & 0xF;
|
||||
|
||||
if (chan != g_midiChan)
|
||||
{
|
||||
Skip(size);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (typeChan & 0xF0)
|
||||
{
|
||||
case 0x90: // note on
|
||||
{
|
||||
int note = ReadInt8();
|
||||
int velocity = ReadInt8();
|
||||
|
||||
if (velocity != 0)
|
||||
{
|
||||
event.type = EventType::Note;
|
||||
event.note = note;
|
||||
event.param1 = velocity;
|
||||
FindNoteEnd(event);
|
||||
if (event.param2 > 0)
|
||||
{
|
||||
if (note < s_minNote)
|
||||
s_minNote = note;
|
||||
if (note > s_maxNote)
|
||||
s_maxNote = note;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xB0: // controller event
|
||||
event.type = EventType::Controller;
|
||||
event.param1 = ReadInt8(); // controller index
|
||||
event.param2 = ReadInt8(); // value
|
||||
break;
|
||||
case 0xC0: // instrument change
|
||||
event.type = EventType::InstrumentChange;
|
||||
event.param1 = ReadInt8(); // instrument
|
||||
event.param2 = 0;
|
||||
break;
|
||||
case 0xE0: // pitch bend
|
||||
event.type = EventType::PitchBend;
|
||||
event.param1 = ReadInt8();
|
||||
event.param2 = ReadInt8();
|
||||
break;
|
||||
default:
|
||||
Skip(size);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (category == MidiEventCategory::SysEx)
|
||||
{
|
||||
SkipEventData();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (category == MidiEventCategory::Meta)
|
||||
{
|
||||
int metaEventType = ReadInt8();
|
||||
SkipEventData();
|
||||
|
||||
if (metaEventType == 0x2F)
|
||||
{
|
||||
event.type = EventType::EndOfTrack;
|
||||
event.param1 = 0;
|
||||
event.param2 = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RaiseError("invalid event");
|
||||
}
|
||||
|
||||
void ReadTrackEvents()
|
||||
{
|
||||
StartTrack();
|
||||
|
||||
s_trackEvents.clear();
|
||||
|
||||
s_minNote = 0xFF;
|
||||
s_maxNote = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Event event = {};
|
||||
|
||||
if (ReadTrackEvent(event))
|
||||
{
|
||||
s_trackEvents.push_back(event);
|
||||
|
||||
if (event.type == EventType::EndOfTrack)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EventCompare(const Event& event1, const Event& event2)
|
||||
{
|
||||
if (event1.time < event2.time)
|
||||
return true;
|
||||
|
||||
if (event1.time > event2.time)
|
||||
return false;
|
||||
|
||||
unsigned event1Type = (unsigned)event1.type;
|
||||
unsigned event2Type = (unsigned)event2.type;
|
||||
|
||||
if (event1.type == EventType::Note)
|
||||
event1Type += event1.note;
|
||||
|
||||
if (event2.type == EventType::Note)
|
||||
event2Type += event2.note;
|
||||
|
||||
if (event1Type < event2Type)
|
||||
return true;
|
||||
|
||||
if (event1Type > event2Type)
|
||||
return false;
|
||||
|
||||
if (event1.type == EventType::EndOfTie)
|
||||
{
|
||||
if (event1.note < event2.note)
|
||||
return true;
|
||||
|
||||
if (event1.note > event2.note)
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<Event>> MergeEvents()
|
||||
{
|
||||
std::unique_ptr<std::vector<Event>> events(new std::vector<Event>());
|
||||
|
||||
unsigned trackEventPos = 0;
|
||||
unsigned seqEventPos = 0;
|
||||
|
||||
while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack
|
||||
&& s_seqEvents[seqEventPos].type != EventType::EndOfTrack)
|
||||
{
|
||||
if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos]))
|
||||
events->push_back(s_trackEvents[trackEventPos++]);
|
||||
else
|
||||
events->push_back(s_seqEvents[seqEventPos++]);
|
||||
}
|
||||
|
||||
while (s_trackEvents[trackEventPos].type != EventType::EndOfTrack)
|
||||
events->push_back(s_trackEvents[trackEventPos++]);
|
||||
|
||||
while (s_seqEvents[seqEventPos].type != EventType::EndOfTrack)
|
||||
events->push_back(s_seqEvents[seqEventPos++]);
|
||||
|
||||
// Push the EndOfTrack event with the larger time.
|
||||
if (EventCompare(s_trackEvents[trackEventPos], s_seqEvents[seqEventPos]))
|
||||
events->push_back(s_seqEvents[seqEventPos]);
|
||||
else
|
||||
events->push_back(s_trackEvents[trackEventPos]);
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
void ConvertTimes(std::vector<Event>& events)
|
||||
{
|
||||
for (Event& event : events)
|
||||
{
|
||||
event.time = (24 * g_clocksPerBeat * event.time) / g_midiTimeDiv;
|
||||
|
||||
if (event.type == EventType::Note)
|
||||
{
|
||||
event.param1 = g_noteVelocityLUT[event.param1];
|
||||
|
||||
std::uint32_t duration = (24 * g_clocksPerBeat * event.param2) / g_midiTimeDiv;
|
||||
|
||||
if (duration == 0)
|
||||
duration = 1;
|
||||
|
||||
if (!g_exactGateTime && duration < 96)
|
||||
duration = g_noteDurationLUT[duration];
|
||||
|
||||
event.param2 = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<Event>> InsertTimingEvents(std::vector<Event>& inEvents)
|
||||
{
|
||||
std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
|
||||
|
||||
Event timingEvent = {};
|
||||
timingEvent.time = 0;
|
||||
timingEvent.type = EventType::TimeSignature;
|
||||
timingEvent.param2 = 96 * g_clocksPerBeat;
|
||||
|
||||
for (const Event& event : inEvents)
|
||||
{
|
||||
while (EventCompare(timingEvent, event))
|
||||
{
|
||||
outEvents->push_back(timingEvent);
|
||||
timingEvent.time += timingEvent.param2;
|
||||
}
|
||||
|
||||
if (event.type == EventType::TimeSignature)
|
||||
{
|
||||
if (g_agbTrack == 1 && event.param2 != timingEvent.param2)
|
||||
{
|
||||
Event originalTimingEvent = event;
|
||||
originalTimingEvent.type = EventType::OriginalTimeSignature;
|
||||
outEvents->push_back(originalTimingEvent);
|
||||
}
|
||||
timingEvent.param2 = event.param2;
|
||||
timingEvent.time = event.time + timingEvent.param2;
|
||||
}
|
||||
|
||||
outEvents->push_back(event);
|
||||
}
|
||||
|
||||
return outEvents;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<Event>> SplitTime(std::vector<Event>& inEvents)
|
||||
{
|
||||
std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
|
||||
|
||||
std::int32_t time = 0;
|
||||
|
||||
for (const Event& event : inEvents)
|
||||
{
|
||||
std::int32_t diff = event.time - time;
|
||||
|
||||
if (diff > 96)
|
||||
{
|
||||
int wholeNoteCount = (diff - 1) / 96;
|
||||
diff -= 96 * wholeNoteCount;
|
||||
|
||||
for (int i = 0; i < wholeNoteCount; i++)
|
||||
{
|
||||
time += 96;
|
||||
Event timeSplitEvent = {};
|
||||
timeSplitEvent.time = time;
|
||||
timeSplitEvent.type = EventType::TimeSplit;
|
||||
outEvents->push_back(timeSplitEvent);
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t lutValue = g_noteDurationLUT[diff];
|
||||
|
||||
if (lutValue != diff)
|
||||
{
|
||||
Event timeSplitEvent = {};
|
||||
timeSplitEvent.time = time + lutValue;
|
||||
timeSplitEvent.type = EventType::TimeSplit;
|
||||
outEvents->push_back(timeSplitEvent);
|
||||
}
|
||||
|
||||
time = event.time;
|
||||
|
||||
outEvents->push_back(event);
|
||||
}
|
||||
|
||||
return outEvents;
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<Event>> CreateTies(std::vector<Event>& inEvents)
|
||||
{
|
||||
std::unique_ptr<std::vector<Event>> outEvents(new std::vector<Event>());
|
||||
|
||||
for (const Event& event : inEvents)
|
||||
{
|
||||
if (event.type == EventType::Note && event.param2 > 96)
|
||||
{
|
||||
Event tieEvent = event;
|
||||
tieEvent.param2 = -1;
|
||||
outEvents->push_back(tieEvent);
|
||||
|
||||
Event eotEvent = {};
|
||||
eotEvent.time = event.time + event.param2;
|
||||
eotEvent.type = EventType::EndOfTie;
|
||||
eotEvent.note = event.note;
|
||||
outEvents->push_back(eotEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
outEvents->push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
return outEvents;
|
||||
}
|
||||
|
||||
void CalculateWaits(std::vector<Event>& events)
|
||||
{
|
||||
g_initialWait = events[0].time;
|
||||
int wholeNoteCount = 0;
|
||||
|
||||
for (unsigned i = 0; i < events.size() && events[i].type != EventType::EndOfTrack; i++)
|
||||
{
|
||||
events[i].time = events[i + 1].time - events[i].time;
|
||||
|
||||
if (events[i].type == EventType::TimeSignature)
|
||||
{
|
||||
events[i].type = EventType::WholeNoteMark;
|
||||
events[i].param2 = wholeNoteCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CalculateCompressionScore(std::vector<Event>& events, int index)
|
||||
{
|
||||
int score = 0;
|
||||
std::uint8_t lastParam1 = events[index].param1;
|
||||
std::uint8_t lastVelocity = 0x80u;
|
||||
EventType lastType = events[index].type;
|
||||
std::int32_t lastDuration = 0x80000000;
|
||||
std::uint8_t lastNote = 0x80u;
|
||||
|
||||
if (events[index].time > 0)
|
||||
score++;
|
||||
|
||||
for (int i = index + 1; !IsPatternBoundary(events[i].type); i++)
|
||||
{
|
||||
if (events[i].type == EventType::Note)
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
if (events[i].note != lastNote)
|
||||
{
|
||||
val++;
|
||||
lastNote = events[i].note;
|
||||
}
|
||||
|
||||
if (events[i].param1 != lastVelocity)
|
||||
{
|
||||
val++;
|
||||
lastVelocity = events[i].param1;
|
||||
}
|
||||
|
||||
std::int32_t duration = events[i].param2;
|
||||
|
||||
if (g_noteDurationLUT[duration] != lastDuration)
|
||||
{
|
||||
val++;
|
||||
lastDuration = g_noteDurationLUT[duration];
|
||||
}
|
||||
|
||||
if (duration != lastDuration)
|
||||
val++;
|
||||
|
||||
if (val == 0)
|
||||
val = 1;
|
||||
|
||||
score += val;
|
||||
}
|
||||
else
|
||||
{
|
||||
lastDuration = 0x80000000;
|
||||
|
||||
if (events[i].type == lastType)
|
||||
{
|
||||
if ((lastType != EventType::Controller && (int)lastType != 0x25 && lastType != EventType::EndOfTie) || events[i].param1 == lastParam1)
|
||||
{
|
||||
score++;
|
||||
}
|
||||
else
|
||||
{
|
||||
score += 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
score += 2;
|
||||
}
|
||||
}
|
||||
|
||||
lastParam1 = events[i].param1;
|
||||
lastType = events[i].type;
|
||||
|
||||
if (events[i].time)
|
||||
++score;
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
bool IsCompressionMatch(std::vector<Event>& events, int index1, int index2)
|
||||
{
|
||||
index1++;
|
||||
index2++;
|
||||
|
||||
do
|
||||
{
|
||||
if (events[index1] != events[index2])
|
||||
return false;
|
||||
|
||||
index1++;
|
||||
index2++;
|
||||
} while (!IsPatternBoundary(events[index1].type));
|
||||
|
||||
return IsPatternBoundary(events[index2].type);
|
||||
}
|
||||
|
||||
void CompressWholeNote(std::vector<Event>& events, int index)
|
||||
{
|
||||
for (int j = index + 1; events[j].type != EventType::EndOfTrack; j++)
|
||||
{
|
||||
while (events[j].type != EventType::WholeNoteMark)
|
||||
{
|
||||
j++;
|
||||
|
||||
if (events[j].type == EventType::EndOfTrack)
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsCompressionMatch(events, index, j))
|
||||
{
|
||||
events[j].type = EventType::Pattern;
|
||||
events[j].param2 = events[index].param2 & 0x7FFFFFFF;
|
||||
events[index].param2 |= 0x80000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Compress(std::vector<Event>& events)
|
||||
{
|
||||
for (int i = 0; events[i].type != EventType::EndOfTrack; i++)
|
||||
{
|
||||
while (events[i].type != EventType::WholeNoteMark)
|
||||
{
|
||||
i++;
|
||||
|
||||
if (events[i].type == EventType::EndOfTrack)
|
||||
return;
|
||||
}
|
||||
|
||||
if (CalculateCompressionScore(events, i) >= 6)
|
||||
{
|
||||
CompressWholeNote(events, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadMidiTracks()
|
||||
{
|
||||
long trackHeaderStart = 14;
|
||||
|
||||
ReadMidiTrackHeader(trackHeaderStart);
|
||||
ReadSeqEvents();
|
||||
|
||||
g_agbTrack = 1;
|
||||
|
||||
for (int midiTrack = 0; midiTrack < g_midiTrackCount; midiTrack++)
|
||||
{
|
||||
trackHeaderStart += ReadMidiTrackHeader(trackHeaderStart);
|
||||
|
||||
for (g_midiChan = 0; g_midiChan < 16; g_midiChan++)
|
||||
{
|
||||
ReadTrackEvents();
|
||||
|
||||
if (s_minNote != 0xFF)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("Track%d = Midi-Ch.%d\n", g_agbTrack, g_midiChan + 1);
|
||||
#endif
|
||||
|
||||
std::unique_ptr<std::vector<Event>> events(MergeEvents());
|
||||
|
||||
// We don't need TEMPO in anything but track 1.
|
||||
if (g_agbTrack == 1)
|
||||
{
|
||||
auto it = std::remove_if(s_seqEvents.begin(), s_seqEvents.end(), [](const Event& event) { return event.type == EventType::Tempo; });
|
||||
s_seqEvents.erase(it, s_seqEvents.end());
|
||||
}
|
||||
|
||||
ConvertTimes(*events);
|
||||
events = InsertTimingEvents(*events);
|
||||
events = CreateTies(*events);
|
||||
std::stable_sort(events->begin(), events->end(), EventCompare);
|
||||
events = SplitTime(*events);
|
||||
CalculateWaits(*events);
|
||||
|
||||
if (g_compressionEnabled)
|
||||
Compress(*events);
|
||||
|
||||
PrintAgbTrack(*events);
|
||||
|
||||
g_agbTrack++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
tools/mid2agb/midi.h
Normal file
87
tools/mid2agb/midi.h
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright(c) 2016 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 MIDI_H
|
||||
#define MIDI_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class MidiFormat
|
||||
{
|
||||
SingleTrack,
|
||||
MultiTrack
|
||||
};
|
||||
|
||||
enum class EventType
|
||||
{
|
||||
EndOfTie = 0x01,
|
||||
Label = 0x11,
|
||||
LoopEnd = 0x12,
|
||||
LoopEndBegin = 0x13,
|
||||
LoopBegin = 0x14,
|
||||
OriginalTimeSignature = 0x15,
|
||||
WholeNoteMark = 0x16,
|
||||
Pattern = 0x17,
|
||||
TimeSignature = 0x18,
|
||||
Tempo = 0x19,
|
||||
InstrumentChange = 0x21,
|
||||
Controller = 0x22,
|
||||
PitchBend = 0x23,
|
||||
KeyShift = 0x31,
|
||||
Note = 0x40,
|
||||
TimeSplit = 0xFE,
|
||||
EndOfTrack = 0xFF,
|
||||
};
|
||||
|
||||
struct Event
|
||||
{
|
||||
std::int32_t time;
|
||||
EventType type;
|
||||
std::uint8_t note;
|
||||
std::uint8_t param1;
|
||||
std::int32_t param2;
|
||||
|
||||
bool operator==(const Event& other)
|
||||
{
|
||||
return (time == other.time
|
||||
&& type == other.type
|
||||
&& note == other.note
|
||||
&& param1 == other.param1
|
||||
&& param2 == other.param2);
|
||||
}
|
||||
|
||||
bool operator!=(const Event& other)
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
void ReadMidiFileHeader();
|
||||
void ReadMidiTracks();
|
||||
|
||||
extern int g_midiChan;
|
||||
extern std::int32_t g_initialWait;
|
||||
|
||||
inline bool IsPatternBoundary(EventType type)
|
||||
{
|
||||
return type == EventType::EndOfTrack || (int)type <= 0x17;
|
||||
}
|
||||
|
||||
#endif // MIDI_H
|
286
tools/mid2agb/tables.cpp
Normal file
286
tools/mid2agb/tables.cpp
Normal file
@ -0,0 +1,286 @@
|
||||
// Copyright(c) 2016 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 "tables.h"
|
||||
|
||||
const int g_noteDurationLUT[] =
|
||||
{
|
||||
0, // 0
|
||||
1, // 1
|
||||
2, // 2
|
||||
3, // 3
|
||||
4, // 4
|
||||
5, // 5
|
||||
6, // 6
|
||||
7, // 7
|
||||
8, // 8
|
||||
9, // 9
|
||||
10, // 10
|
||||
11, // 11
|
||||
12, // 12
|
||||
13, // 13
|
||||
14, // 14
|
||||
15, // 15
|
||||
16, // 16
|
||||
17, // 17
|
||||
18, // 18
|
||||
19, // 19
|
||||
20, // 20
|
||||
21, // 21
|
||||
22, // 22
|
||||
23, // 23
|
||||
24, // 24
|
||||
24, // 25
|
||||
24, // 26
|
||||
24, // 27
|
||||
28, // 28
|
||||
28, // 29
|
||||
30, // 30
|
||||
30, // 31
|
||||
32, // 32
|
||||
32, // 33
|
||||
32, // 34
|
||||
32, // 35
|
||||
36, // 36
|
||||
36, // 37
|
||||
36, // 38
|
||||
36, // 39
|
||||
40, // 40
|
||||
40, // 41
|
||||
42, // 42
|
||||
42, // 43
|
||||
44, // 44
|
||||
44, // 45
|
||||
44, // 46
|
||||
44, // 47
|
||||
48, // 48
|
||||
48, // 49
|
||||
48, // 50
|
||||
48, // 51
|
||||
52, // 52
|
||||
52, // 53
|
||||
54, // 54
|
||||
54, // 55
|
||||
56, // 56
|
||||
56, // 57
|
||||
56, // 58
|
||||
56, // 59
|
||||
60, // 60
|
||||
60, // 61
|
||||
60, // 62
|
||||
60, // 63
|
||||
64, // 64
|
||||
64, // 65
|
||||
66, // 66
|
||||
66, // 67
|
||||
68, // 68
|
||||
68, // 69
|
||||
68, // 70
|
||||
68, // 71
|
||||
72, // 72
|
||||
72, // 73
|
||||
72, // 74
|
||||
72, // 75
|
||||
76, // 76
|
||||
76, // 77
|
||||
78, // 78
|
||||
78, // 79
|
||||
80, // 80
|
||||
80, // 81
|
||||
80, // 82
|
||||
80, // 83
|
||||
84, // 84
|
||||
84, // 85
|
||||
84, // 86
|
||||
84, // 87
|
||||
88, // 88
|
||||
88, // 89
|
||||
90, // 90
|
||||
90, // 91
|
||||
92, // 92
|
||||
92, // 93
|
||||
92, // 94
|
||||
92, // 95
|
||||
96, // 96
|
||||
};
|
||||
|
||||
const int g_noteVelocityLUT[] =
|
||||
{
|
||||
0, // 0
|
||||
4, // 1
|
||||
4, // 2
|
||||
4, // 3
|
||||
4, // 4
|
||||
8, // 5
|
||||
8, // 6
|
||||
8, // 7
|
||||
8, // 8
|
||||
12, // 9
|
||||
12, // 10
|
||||
12, // 11
|
||||
12, // 12
|
||||
16, // 13
|
||||
16, // 14
|
||||
16, // 15
|
||||
16, // 16
|
||||
20, // 17
|
||||
20, // 18
|
||||
20, // 19
|
||||
20, // 20
|
||||
24, // 21
|
||||
24, // 22
|
||||
24, // 23
|
||||
24, // 24
|
||||
28, // 25
|
||||
28, // 26
|
||||
28, // 27
|
||||
28, // 28
|
||||
32, // 29
|
||||
32, // 30
|
||||
32, // 31
|
||||
32, // 32
|
||||
36, // 33
|
||||
36, // 34
|
||||
36, // 35
|
||||
36, // 36
|
||||
40, // 37
|
||||
40, // 38
|
||||
40, // 39
|
||||
40, // 40
|
||||
44, // 41
|
||||
44, // 42
|
||||
44, // 43
|
||||
44, // 44
|
||||
48, // 45
|
||||
48, // 46
|
||||
48, // 47
|
||||
48, // 48
|
||||
52, // 49
|
||||
52, // 50
|
||||
52, // 51
|
||||
52, // 52
|
||||
56, // 53
|
||||
56, // 54
|
||||
56, // 55
|
||||
56, // 56
|
||||
60, // 57
|
||||
60, // 58
|
||||
60, // 59
|
||||
60, // 60
|
||||
64, // 61
|
||||
64, // 62
|
||||
64, // 63
|
||||
64, // 64
|
||||
68, // 65
|
||||
68, // 66
|
||||
68, // 67
|
||||
68, // 68
|
||||
72, // 69
|
||||
72, // 70
|
||||
72, // 71
|
||||
72, // 72
|
||||
76, // 73
|
||||
76, // 74
|
||||
76, // 75
|
||||
76, // 76
|
||||
80, // 77
|
||||
80, // 78
|
||||
80, // 79
|
||||
80, // 80
|
||||
84, // 81
|
||||
84, // 82
|
||||
84, // 83
|
||||
84, // 84
|
||||
88, // 85
|
||||
88, // 86
|
||||
88, // 87
|
||||
88, // 88
|
||||
92, // 89
|
||||
92, // 90
|
||||
92, // 91
|
||||
92, // 92
|
||||
96, // 93
|
||||
96, // 94
|
||||
96, // 95
|
||||
96, // 96
|
||||
100, // 97
|
||||
100, // 98
|
||||
100, // 99
|
||||
100, // 100
|
||||
104, // 101
|
||||
104, // 102
|
||||
104, // 103
|
||||
104, // 104
|
||||
108, // 105
|
||||
108, // 106
|
||||
108, // 107
|
||||
108, // 108
|
||||
112, // 109
|
||||
112, // 110
|
||||
112, // 111
|
||||
112, // 112
|
||||
116, // 113
|
||||
116, // 114
|
||||
116, // 115
|
||||
116, // 116
|
||||
120, // 117
|
||||
120, // 118
|
||||
120, // 119
|
||||
120, // 120
|
||||
124, // 121
|
||||
124, // 122
|
||||
124, // 123
|
||||
124, // 124
|
||||
127, // 125
|
||||
127, // 126
|
||||
127, // 127
|
||||
};
|
||||
|
||||
const char* g_noteTable[] =
|
||||
{
|
||||
"Cn%01u ",
|
||||
"Cs%01u ",
|
||||
"Dn%01u ",
|
||||
"Ds%01u ",
|
||||
"En%01u ",
|
||||
"Fn%01u ",
|
||||
"Fs%01u ",
|
||||
"Gn%01u ",
|
||||
"Gs%01u ",
|
||||
"An%01u ",
|
||||
"As%01u ",
|
||||
"Bn%01u ",
|
||||
};
|
||||
|
||||
const char* g_minusNoteTable[] =
|
||||
{
|
||||
"CnM%01u",
|
||||
"CsM%01u",
|
||||
"DnM%01u",
|
||||
"DsM%01u",
|
||||
"EnM%01u",
|
||||
"FnM%01u",
|
||||
"FsM%01u",
|
||||
"GnM%01u",
|
||||
"GsM%01u",
|
||||
"AnM%01u",
|
||||
"AsM%01u",
|
||||
"BnM%01u",
|
||||
};
|
29
tools/mid2agb/tables.h
Normal file
29
tools/mid2agb/tables.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright(c) 2016 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 TABLES_H
|
||||
#define TABLES_H
|
||||
|
||||
extern const int g_noteDurationLUT[];
|
||||
extern const int g_noteVelocityLUT[];
|
||||
extern const char* g_noteTable[];
|
||||
extern const char* g_minusNoteTable[];
|
||||
|
||||
#endif // TABLES_H
|
1
tools/preproc/.gitignore
vendored
Normal file
1
tools/preproc/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
preproc
|
19
tools/preproc/LICENSE
Normal file
19
tools/preproc/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 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.
|
17
tools/preproc/Makefile
Normal file
17
tools/preproc/Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
CXX := g++
|
||||
|
||||
CXXFLAGS := -std=c++11 -O2 -s -Wall -Wno-switch -Werror
|
||||
|
||||
SRCS := asm_file.cpp c_file.cpp charmap.cpp preproc.cpp string_parser.cpp \
|
||||
utf8.cpp
|
||||
|
||||
HEADERS := asm_file.h c_file.h char_util.h charmap.h preproc.h string_parser.h \
|
||||
utf8.h
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
preproc: $(SRCS) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) preproc preproc.exe
|
529
tools/preproc/asm_file.cpp
Normal file
529
tools/preproc/asm_file.cpp
Normal file
@ -0,0 +1,529 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include "preproc.h"
|
||||
#include "asm_file.h"
|
||||
#include "char_util.h"
|
||||
#include "utf8.h"
|
||||
#include "string_parser.h"
|
||||
|
||||
AsmFile::AsmFile(std::string filename) : m_filename(filename)
|
||||
{
|
||||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
|
||||
m_size = std::ftell(fp);
|
||||
|
||||
if (m_size < 0)
|
||||
FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
|
||||
|
||||
m_buffer = new char[m_size + 1];
|
||||
|
||||
std::rewind(fp);
|
||||
|
||||
if (std::fread(m_buffer, m_size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
|
||||
|
||||
m_buffer[m_size] = 0;
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
m_pos = 0;
|
||||
m_lineNum = 1;
|
||||
m_lineStart = 0;
|
||||
|
||||
RemoveComments();
|
||||
}
|
||||
|
||||
AsmFile::AsmFile(AsmFile&& other) : m_filename(std::move(other.m_filename))
|
||||
{
|
||||
m_buffer = other.m_buffer;
|
||||
m_pos = other.m_pos;
|
||||
m_size = other.m_size;
|
||||
m_lineNum = other.m_lineNum;
|
||||
m_lineStart = other.m_lineStart;
|
||||
|
||||
other.m_buffer = nullptr;
|
||||
}
|
||||
|
||||
AsmFile::~AsmFile()
|
||||
{
|
||||
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 AsmFile::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] == '@' && (pos == 0 || 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if we're at a particular directive and if so, consumes it.
|
||||
// Returns whether the directive was found.
|
||||
bool AsmFile::CheckForDirective(std::string name)
|
||||
{
|
||||
long i;
|
||||
long length = static_cast<long>(name.length());
|
||||
|
||||
for (i = 0; i < length && m_pos + i < m_size; i++)
|
||||
if (name[i] != m_buffer[m_pos + i])
|
||||
return false;
|
||||
|
||||
if (i < length)
|
||||
return false;
|
||||
|
||||
m_pos += length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if we're at a known directive and if so, consumes it.
|
||||
// Returns which directive was found.
|
||||
Directive AsmFile::GetDirective()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (CheckForDirective(".include"))
|
||||
return Directive::Include;
|
||||
else if (CheckForDirective(".string"))
|
||||
return Directive::String;
|
||||
else if (CheckForDirective(".braille"))
|
||||
return Directive::Braille;
|
||||
else
|
||||
return Directive::Unknown;
|
||||
}
|
||||
|
||||
// Checks if we're at label that ends with '::'.
|
||||
// Returns the name if so and an empty string if not.
|
||||
std::string AsmFile::GetGlobalLabel()
|
||||
{
|
||||
long start = m_pos;
|
||||
long pos = m_pos;
|
||||
|
||||
if (IsIdentifierStartingChar(m_buffer[pos]))
|
||||
{
|
||||
pos++;
|
||||
|
||||
while (IsIdentifierChar(m_buffer[pos]))
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (m_buffer[pos] == ':' && m_buffer[pos + 1] == ':')
|
||||
{
|
||||
m_pos = pos + 2;
|
||||
ExpectEmptyRestOfLine();
|
||||
return std::string(&m_buffer[start], pos - start);
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// Skips tabs and spaces.
|
||||
void AsmFile::SkipWhitespace()
|
||||
{
|
||||
while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
// Reads include path.
|
||||
std::string AsmFile::ReadPath()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] != '"')
|
||||
RaiseError("expected file path");
|
||||
|
||||
m_pos++;
|
||||
|
||||
int length = 0;
|
||||
long startPos = m_pos;
|
||||
|
||||
while (m_buffer[m_pos] != '"')
|
||||
{
|
||||
unsigned char c = m_buffer[m_pos++];
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseError("unexpected EOF in include string");
|
||||
else
|
||||
RaiseError("unexpected null character in include string");
|
||||
}
|
||||
|
||||
if (!IsAsciiPrintable(c))
|
||||
RaiseError("unexpected character '\\x%02X' in include string", c);
|
||||
|
||||
// Don't bother allowing any escape sequences.
|
||||
if (c == '\\')
|
||||
{
|
||||
c = m_buffer[m_pos];
|
||||
RaiseError("unexpected escape '\\%c' in include string", c);
|
||||
}
|
||||
|
||||
length++;
|
||||
|
||||
if (length > kMaxPath)
|
||||
RaiseError("path is too long");
|
||||
}
|
||||
|
||||
m_pos++; // Go past the right quote.
|
||||
|
||||
ExpectEmptyRestOfLine();
|
||||
|
||||
return std::string(&m_buffer[startPos], length);
|
||||
}
|
||||
|
||||
// Reads a charmap string.
|
||||
int AsmFile::ReadString(unsigned char* s)
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
SkipWhitespace();
|
||||
|
||||
if (ConsumeComma())
|
||||
{
|
||||
SkipWhitespace();
|
||||
int padLength = ReadPadLength();
|
||||
|
||||
while (length < padLength)
|
||||
{
|
||||
s[length++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ExpectEmptyRestOfLine();
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
int AsmFile::ReadBraille(unsigned char* s)
|
||||
{
|
||||
static std::map<char, unsigned char> encoding =
|
||||
{
|
||||
{ 'A', 0x01 },
|
||||
{ 'B', 0x05 },
|
||||
{ 'C', 0x03 },
|
||||
{ 'D', 0x0B },
|
||||
{ 'E', 0x09 },
|
||||
{ 'F', 0x07 },
|
||||
{ 'G', 0x0F },
|
||||
{ 'H', 0x0D },
|
||||
{ 'I', 0x06 },
|
||||
{ 'J', 0x0E },
|
||||
{ 'K', 0x11 },
|
||||
{ 'L', 0x15 },
|
||||
{ 'M', 0x13 },
|
||||
{ 'N', 0x1B },
|
||||
{ 'O', 0x19 },
|
||||
{ 'P', 0x17 },
|
||||
{ 'Q', 0x1F },
|
||||
{ 'R', 0x1D },
|
||||
{ 'S', 0x16 },
|
||||
{ 'T', 0x1E },
|
||||
{ 'U', 0x31 },
|
||||
{ 'V', 0x35 },
|
||||
{ 'W', 0x2E },
|
||||
{ 'X', 0x33 },
|
||||
{ 'Y', 0x3B },
|
||||
{ 'Z', 0x39 },
|
||||
{ ' ', 0x00 },
|
||||
{ ',', 0x04 },
|
||||
{ '.', 0x2C },
|
||||
{ '$', 0xFF },
|
||||
};
|
||||
|
||||
SkipWhitespace();
|
||||
|
||||
int length = 0;
|
||||
|
||||
if (m_buffer[m_pos] != '"')
|
||||
RaiseError("expected braille string literal");
|
||||
|
||||
m_pos++;
|
||||
|
||||
while (m_buffer[m_pos] != '"')
|
||||
{
|
||||
if (length == kMaxStringLength)
|
||||
RaiseError("mapped string longer than %d bytes", kMaxStringLength);
|
||||
|
||||
if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == 'n')
|
||||
{
|
||||
s[length++] = 0xFE;
|
||||
m_pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
char c = m_buffer[m_pos];
|
||||
|
||||
if (encoding.count(c) == 0)
|
||||
{
|
||||
if (IsAsciiPrintable(c))
|
||||
RaiseError("character '%c' not valid in braille string", m_buffer[m_pos]);
|
||||
else
|
||||
RaiseError("character '\\x%02X' not valid in braille string", m_buffer[m_pos]);
|
||||
}
|
||||
|
||||
s[length++] = encoding[c];
|
||||
m_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
m_pos++; // Go past the right quote.
|
||||
|
||||
ExpectEmptyRestOfLine();
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
// If we're at a comma, consumes it.
|
||||
// Returns whether a comma was found.
|
||||
bool AsmFile::ConsumeComma()
|
||||
{
|
||||
if (m_buffer[m_pos] == ',')
|
||||
{
|
||||
m_pos++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts digit character to numerical value.
|
||||
static int ConvertDigit(char c, int radix)
|
||||
{
|
||||
int digit;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
digit = c - '0';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
digit = 10 + c - 'A';
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
digit = 10 + c - 'a';
|
||||
else
|
||||
return -1;
|
||||
|
||||
return (digit < radix) ? digit : -1;
|
||||
}
|
||||
|
||||
// Reads an integer. If the integer is greater than maxValue, it returns -1.
|
||||
int AsmFile::ReadPadLength()
|
||||
{
|
||||
if (!IsAsciiDigit(m_buffer[m_pos]))
|
||||
RaiseError("expected integer");
|
||||
|
||||
int radix = 10;
|
||||
|
||||
if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x')
|
||||
{
|
||||
radix = 16;
|
||||
m_pos += 2;
|
||||
}
|
||||
|
||||
unsigned n = 0;
|
||||
int digit;
|
||||
|
||||
while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1)
|
||||
{
|
||||
n = n * radix + digit;
|
||||
|
||||
if (n > kMaxStringLength)
|
||||
RaiseError("pad length greater than maximum length (%d)", kMaxStringLength);
|
||||
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
// Outputs the current line and moves to the next one.
|
||||
void AsmFile::OutputLine()
|
||||
{
|
||||
while (m_buffer[m_pos] != '\n' && m_buffer[m_pos] != 0)
|
||||
m_pos++;
|
||||
|
||||
if (m_buffer[m_pos] == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
{
|
||||
RaiseWarning("file doesn't end with newline");
|
||||
puts(&m_buffer[m_lineStart]);
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError("unexpected null character");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buffer[m_pos] = 0;
|
||||
puts(&m_buffer[m_lineStart]);
|
||||
m_buffer[m_pos] = '\n';
|
||||
m_pos++;
|
||||
m_lineStart = m_pos;
|
||||
m_lineNum++;
|
||||
}
|
||||
}
|
||||
|
||||
// Asserts that the rest of the line is empty and moves to the next one.
|
||||
void AsmFile::ExpectEmptyRestOfLine()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseWarning("file doesn't end with newline");
|
||||
else
|
||||
RaiseError("unexpected null character");
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\n')
|
||||
{
|
||||
m_pos++;
|
||||
m_lineStart = m_pos;
|
||||
m_lineNum++;
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\r')
|
||||
{
|
||||
RaiseError("only Unix-style LF newlines are supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError("junk at end of line");
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if we're at the end of the file.
|
||||
bool AsmFile::IsAtEnd()
|
||||
{
|
||||
return (m_pos >= m_size);
|
||||
}
|
||||
|
||||
// Output the current location to set gas's logical file and line numbers.
|
||||
void AsmFile::OutputLocation()
|
||||
{
|
||||
std::printf("# %ld \"%s\"\n", m_lineNum, m_filename.c_str());
|
||||
}
|
||||
|
||||
// Reports a diagnostic message.
|
||||
void AsmFile::ReportDiagnostic(const char* type, const char* format, std::va_list args)
|
||||
{
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
std::vsnprintf(buffer, bufferSize, format, args);
|
||||
std::fprintf(stderr, "%s:%ld: %s: %s\n", m_filename.c_str(), m_lineNum, type, buffer);
|
||||
}
|
||||
|
||||
#define DO_REPORT(type) \
|
||||
do \
|
||||
{ \
|
||||
std::va_list args; \
|
||||
va_start(args, format); \
|
||||
ReportDiagnostic(type, format, args); \
|
||||
va_end(args); \
|
||||
} while (0)
|
||||
|
||||
// Reports an error diagnostic and terminates the program.
|
||||
void AsmFile::RaiseError(const char* format, ...)
|
||||
{
|
||||
DO_REPORT("error");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// Reports a warning diagnostic.
|
||||
void AsmFile::RaiseWarning(const char* format, ...)
|
||||
{
|
||||
DO_REPORT("warning");
|
||||
}
|
72
tools/preproc/asm_file.h
Normal file
72
tools/preproc/asm_file.h
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "preproc.h"
|
||||
|
||||
enum class Directive
|
||||
{
|
||||
Include,
|
||||
String,
|
||||
Braille,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class AsmFile
|
||||
{
|
||||
public:
|
||||
AsmFile(std::string filename);
|
||||
AsmFile(AsmFile&& other);
|
||||
AsmFile(const AsmFile&) = delete;
|
||||
~AsmFile();
|
||||
Directive GetDirective();
|
||||
std::string GetGlobalLabel();
|
||||
std::string ReadPath();
|
||||
int ReadString(unsigned char* s);
|
||||
int ReadBraille(unsigned char* s);
|
||||
bool IsAtEnd();
|
||||
void OutputLine();
|
||||
void OutputLocation();
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
long m_pos;
|
||||
long m_size;
|
||||
long m_lineNum;
|
||||
long m_lineStart;
|
||||
std::string m_filename;
|
||||
|
||||
bool ConsumeComma();
|
||||
int ReadPadLength();
|
||||
void RemoveComments();
|
||||
bool CheckForDirective(std::string name);
|
||||
void SkipWhitespace();
|
||||
void ExpectEmptyRestOfLine();
|
||||
void ReportDiagnostic(const char* type, const char* format, std::va_list args);
|
||||
void RaiseError(const char* format, ...);
|
||||
void RaiseWarning(const char* format, ...);
|
||||
};
|
||||
|
||||
#endif // ASM_FILE_H
|
421
tools/preproc/c_file.cpp
Normal file
421
tools/preproc/c_file.cpp
Normal file
@ -0,0 +1,421 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "preproc.h"
|
||||
#include "c_file.h"
|
||||
#include "char_util.h"
|
||||
#include "utf8.h"
|
||||
#include "string_parser.h"
|
||||
|
||||
CFile::CFile(std::string filename) : m_filename(filename)
|
||||
{
|
||||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
|
||||
m_size = std::ftell(fp);
|
||||
|
||||
if (m_size < 0)
|
||||
FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
|
||||
|
||||
m_buffer = new char[m_size + 1];
|
||||
|
||||
std::rewind(fp);
|
||||
|
||||
if (std::fread(m_buffer, m_size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
|
||||
|
||||
m_buffer[m_size] = 0;
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
m_pos = 0;
|
||||
m_lineNum = 1;
|
||||
}
|
||||
|
||||
CFile::CFile(CFile&& other) : m_filename(std::move(other.m_filename))
|
||||
{
|
||||
m_buffer = other.m_buffer;
|
||||
m_pos = other.m_pos;
|
||||
m_size = other.m_size;
|
||||
m_lineNum = other.m_lineNum;
|
||||
|
||||
other.m_buffer = nullptr;
|
||||
}
|
||||
|
||||
CFile::~CFile()
|
||||
{
|
||||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
void CFile::Preproc()
|
||||
{
|
||||
char stringChar = 0;
|
||||
|
||||
while (m_pos < m_size)
|
||||
{
|
||||
if (stringChar)
|
||||
{
|
||||
if (m_buffer[m_pos] == stringChar)
|
||||
{
|
||||
std::putchar(stringChar);
|
||||
m_pos++;
|
||||
stringChar = 0;
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\\' && m_buffer[m_pos + 1] == stringChar)
|
||||
{
|
||||
std::putchar('\\');
|
||||
std::putchar(stringChar);
|
||||
m_pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_buffer[m_pos] == '\n')
|
||||
m_lineNum++;
|
||||
std::putchar(m_buffer[m_pos]);
|
||||
m_pos++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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++;
|
||||
std::putchar('\n');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_buffer[m_pos] == '\n')
|
||||
{
|
||||
m_pos++;
|
||||
m_lineNum++;
|
||||
std::putchar('\n');
|
||||
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
|
||||
{
|
||||
m_pos += stringParser.ParseString(m_pos, s, length);
|
||||
}
|
||||
catch (std::runtime_error e)
|
||||
{
|
||||
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;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
std::unique_ptr<unsigned char[]> CFile::ReadWholeFile(const std::string& path, int& size)
|
||||
{
|
||||
FILE* fp = std::fopen(path.c_str(), "rb");
|
||||
|
||||
if (fp == nullptr)
|
||||
RaiseError("Failed to open \"%s\" for reading.\n", path.c_str());
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
|
||||
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 (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] != '(')
|
||||
{
|
||||
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.
|
||||
void CFile::ReportDiagnostic(const char* type, const char* format, std::va_list args)
|
||||
{
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
std::vsnprintf(buffer, bufferSize, format, args);
|
||||
std::fprintf(stderr, "%s:%ld: %s: %s\n", m_filename.c_str(), m_lineNum, type, buffer);
|
||||
}
|
||||
|
||||
#define DO_REPORT(type) \
|
||||
do \
|
||||
{ \
|
||||
std::va_list args; \
|
||||
va_start(args, format); \
|
||||
ReportDiagnostic(type, format, args); \
|
||||
va_end(args); \
|
||||
} while (0)
|
||||
|
||||
// Reports an error diagnostic and terminates the program.
|
||||
void CFile::RaiseError(const char* format, ...)
|
||||
{
|
||||
DO_REPORT("error");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// Reports a warning diagnostic.
|
||||
void CFile::RaiseWarning(const char* format, ...)
|
||||
{
|
||||
DO_REPORT("warning");
|
||||
}
|
58
tools/preproc/c_file.h
Normal file
58
tools/preproc/c_file.h
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include "preproc.h"
|
||||
|
||||
class CFile
|
||||
{
|
||||
public:
|
||||
CFile(std::string filename);
|
||||
CFile(CFile&& other);
|
||||
CFile(const CFile&) = delete;
|
||||
~CFile();
|
||||
void Preproc();
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
long m_pos;
|
||||
long m_size;
|
||||
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, ...);
|
||||
};
|
||||
|
||||
#endif // C_FILE_H
|
71
tools/preproc/char_util.h
Normal file
71
tools/preproc/char_util.h
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright(c) 2016 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 CHAR_UTIL_H
|
||||
#define CHAR_UTIL_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
inline bool IsAscii(unsigned char c)
|
||||
{
|
||||
return (c < 128);
|
||||
}
|
||||
|
||||
inline bool IsAsciiAlpha(unsigned char c)
|
||||
{
|
||||
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
||||
}
|
||||
|
||||
inline bool IsAsciiDigit(unsigned char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
inline bool IsAsciiHexDigit(unsigned char c)
|
||||
{
|
||||
return ((c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'f')
|
||||
|| (c >= 'A' && c <= 'F'));
|
||||
}
|
||||
|
||||
inline bool IsAsciiAlphanum(unsigned char c)
|
||||
{
|
||||
return (IsAsciiAlpha(c) || IsAsciiDigit(c));
|
||||
}
|
||||
|
||||
inline bool IsAsciiPrintable(unsigned char c)
|
||||
{
|
||||
return (c >= ' ' && c <= '~');
|
||||
}
|
||||
|
||||
// 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 a C identifier or the identifier of a "{FOO}" constant in strings.
|
||||
inline bool IsIdentifierChar(unsigned char c)
|
||||
{
|
||||
return IsAsciiAlphanum(c) || c == '_';
|
||||
}
|
||||
|
||||
#endif // CHAR_UTIL_H
|
408
tools/preproc/charmap.cpp
Normal file
408
tools/preproc/charmap.cpp
Normal file
@ -0,0 +1,408 @@
|
||||
// Copyright(c) 2016 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 <cstdint>
|
||||
#include <cstdarg>
|
||||
#include "preproc.h"
|
||||
#include "charmap.h"
|
||||
#include "char_util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
enum LhsType
|
||||
{
|
||||
Char,
|
||||
Escape,
|
||||
Constant,
|
||||
None
|
||||
};
|
||||
|
||||
struct Lhs
|
||||
{
|
||||
LhsType type;
|
||||
std::string name;
|
||||
std::int32_t code;
|
||||
};
|
||||
|
||||
class CharmapReader
|
||||
{
|
||||
public:
|
||||
CharmapReader(std::string filename);
|
||||
CharmapReader(const CharmapReader&) = delete;
|
||||
~CharmapReader();
|
||||
Lhs ReadLhs();
|
||||
void ExpectEqualsSign();
|
||||
std::string ReadSequence();
|
||||
void ExpectEmptyRestOfLine();
|
||||
void RaiseError(const char* format, ...);
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
long m_pos;
|
||||
long m_size;
|
||||
long m_lineNum;
|
||||
std::string m_filename;
|
||||
|
||||
void RemoveComments();
|
||||
std::string ReadConstant();
|
||||
void SkipWhitespace();
|
||||
};
|
||||
|
||||
CharmapReader::CharmapReader(std::string filename) : m_filename(filename)
|
||||
{
|
||||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
|
||||
m_size = std::ftell(fp);
|
||||
|
||||
if (m_size < 0)
|
||||
FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
|
||||
|
||||
m_buffer = new char[m_size + 1];
|
||||
|
||||
std::rewind(fp);
|
||||
|
||||
if (std::fread(m_buffer, m_size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
|
||||
|
||||
m_buffer[m_size] = 0;
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
m_pos = 0;
|
||||
m_lineNum = 1;
|
||||
|
||||
RemoveComments();
|
||||
}
|
||||
|
||||
CharmapReader::~CharmapReader()
|
||||
{
|
||||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
Lhs CharmapReader::ReadLhs()
|
||||
{
|
||||
Lhs lhs;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] == '\n')
|
||||
{
|
||||
m_pos++;
|
||||
m_lineNum++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_buffer[m_pos] == '\'')
|
||||
{
|
||||
m_pos++;
|
||||
|
||||
bool isEscape = (m_buffer[m_pos] == '\\');
|
||||
|
||||
if (isEscape)
|
||||
{
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
unsigned char c = m_buffer[m_pos];
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseError("unexpected EOF in UTF-8 character literal");
|
||||
else
|
||||
RaiseError("unexpected null character in UTF-8 character literal");
|
||||
}
|
||||
|
||||
if (IsAscii(c) && !IsAsciiPrintable(c))
|
||||
RaiseError("unexpected character U+%X in UTF-8 character literal", c);
|
||||
|
||||
UnicodeChar unicodeChar = DecodeUtf8(&m_buffer[m_pos]);
|
||||
std::int32_t code = unicodeChar.code;
|
||||
|
||||
if (code == -1)
|
||||
RaiseError("invalid encoding in UTF-8 character literal");
|
||||
|
||||
m_pos += unicodeChar.encodingLength;
|
||||
|
||||
if (m_buffer[m_pos] != '\'')
|
||||
RaiseError("unterminated character literal");
|
||||
|
||||
m_pos++;
|
||||
|
||||
lhs.code = code;
|
||||
|
||||
if (isEscape)
|
||||
{
|
||||
if (code >= 128)
|
||||
RaiseError("escapes using non-ASCII characters are invalid");
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case '\'':
|
||||
lhs.type = LhsType::Char;
|
||||
break;
|
||||
case '\\':
|
||||
lhs.type = LhsType::Char;
|
||||
case '"':
|
||||
RaiseError("cannot escape double quote");
|
||||
break;
|
||||
default:
|
||||
lhs.type = LhsType::Escape;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code == '\'')
|
||||
RaiseError("empty character literal");
|
||||
|
||||
lhs.type = LhsType::Char;
|
||||
}
|
||||
}
|
||||
else if (IsIdentifierStartingChar(m_buffer[m_pos]))
|
||||
{
|
||||
lhs.type = LhsType::Constant;
|
||||
lhs.name = ReadConstant();
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\r')
|
||||
{
|
||||
RaiseError("only Unix-style LF newlines are supported");
|
||||
}
|
||||
else if (m_buffer[m_pos] == 0)
|
||||
{
|
||||
if (m_pos < m_size)
|
||||
RaiseError("unexpected null character");
|
||||
lhs.type = LhsType::None;
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError("junk at start of line");
|
||||
}
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
void CharmapReader::ExpectEqualsSign()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] != '=')
|
||||
RaiseError("expected equals sign");
|
||||
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
static unsigned int ConvertHexDigit(char c)
|
||||
{
|
||||
unsigned int digit = 0;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
digit = c - '0';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
digit = 10 + c - 'A';
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
digit = 10 + c - 'a';
|
||||
|
||||
return digit;
|
||||
}
|
||||
|
||||
std::string CharmapReader::ReadSequence()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
long startPos = m_pos;
|
||||
|
||||
unsigned int length = 0;
|
||||
|
||||
while (IsAsciiHexDigit(m_buffer[m_pos]) && IsAsciiHexDigit(m_buffer[m_pos + 1]))
|
||||
{
|
||||
m_pos += 2;
|
||||
length++;
|
||||
|
||||
if (length > kMaxCharmapSequenceLength)
|
||||
RaiseError("byte sequence too long (max is %lu bytes)", kMaxCharmapSequenceLength);
|
||||
|
||||
SkipWhitespace();
|
||||
}
|
||||
|
||||
if (IsAsciiHexDigit(m_buffer[m_pos]))
|
||||
RaiseError("each byte must have 2 hex digits");
|
||||
|
||||
if (length == 0)
|
||||
RaiseError("expected byte sequence");
|
||||
|
||||
std::string sequence;
|
||||
sequence.reserve(length);
|
||||
|
||||
m_pos = startPos;
|
||||
|
||||
for (unsigned int i = 0; i < length; i++)
|
||||
{
|
||||
unsigned int digit1 = ConvertHexDigit(m_buffer[m_pos]);
|
||||
unsigned int digit2 = ConvertHexDigit(m_buffer[m_pos + 1]);
|
||||
unsigned char byte = digit1 * 16 + digit2;
|
||||
sequence += byte;
|
||||
|
||||
m_pos += 2;
|
||||
SkipWhitespace();
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
void CharmapReader::ExpectEmptyRestOfLine()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] == 0)
|
||||
{
|
||||
if (m_pos < m_size)
|
||||
RaiseError("unexpected null character");
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\n')
|
||||
{
|
||||
m_pos++;
|
||||
m_lineNum++;
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\r')
|
||||
{
|
||||
RaiseError("only Unix-style LF newlines are supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError("junk at end of line");
|
||||
}
|
||||
}
|
||||
|
||||
void CharmapReader::RaiseError(const char* format, ...)
|
||||
{
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
std::vsnprintf(buffer, bufferSize, format, args);
|
||||
va_end(args);
|
||||
|
||||
std::fprintf(stderr, "%s:%ld: error: %s\n", m_filename.c_str(), m_lineNum, buffer);
|
||||
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
void CharmapReader::RemoveComments()
|
||||
{
|
||||
long pos = 0;
|
||||
bool inString = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (m_buffer[pos] == 0)
|
||||
return;
|
||||
|
||||
if (inString)
|
||||
{
|
||||
if (m_buffer[pos] == '\\' && m_buffer[pos + 1] == '\'')
|
||||
{
|
||||
pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_buffer[pos] == '\'')
|
||||
inString = false;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
else if (m_buffer[pos] == '@')
|
||||
{
|
||||
while (m_buffer[pos] != '\n' && m_buffer[pos] != 0)
|
||||
m_buffer[pos++] = ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_buffer[pos] == '\'')
|
||||
inString = true;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string CharmapReader::ReadConstant()
|
||||
{
|
||||
long startPos = m_pos;
|
||||
|
||||
while (IsIdentifierChar(m_buffer[m_pos]))
|
||||
m_pos++;
|
||||
|
||||
return std::string(&m_buffer[startPos], m_pos - startPos);
|
||||
}
|
||||
|
||||
void CharmapReader::SkipWhitespace()
|
||||
{
|
||||
while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
Charmap::Charmap(std::string filename)
|
||||
{
|
||||
CharmapReader reader(filename);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Lhs lhs = reader.ReadLhs();
|
||||
|
||||
if (lhs.type == LhsType::None)
|
||||
return;
|
||||
|
||||
reader.ExpectEqualsSign();
|
||||
|
||||
std::string sequence = reader.ReadSequence();
|
||||
|
||||
switch (lhs.type)
|
||||
{
|
||||
case LhsType::Char:
|
||||
if (m_chars.find(lhs.code) != m_chars.end())
|
||||
reader.RaiseError("redefining char");
|
||||
m_chars[lhs.code] = sequence;
|
||||
break;
|
||||
case LhsType::Escape:
|
||||
if (m_escapes[lhs.code].length() != 0)
|
||||
reader.RaiseError("redefining escape");
|
||||
m_escapes[lhs.code] = sequence;
|
||||
break;
|
||||
case LhsType::Constant:
|
||||
if (m_constants.find(lhs.name) != m_constants.end())
|
||||
reader.RaiseError("redefining constant");
|
||||
m_constants[lhs.name] = sequence;
|
||||
break;
|
||||
}
|
||||
|
||||
reader.ExpectEmptyRestOfLine();
|
||||
}
|
||||
}
|
64
tools/preproc/charmap.h
Normal file
64
tools/preproc/charmap.h
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright(c) 2016 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 CHARMAP_H
|
||||
#define CHARMAP_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class Charmap
|
||||
{
|
||||
public:
|
||||
Charmap(std::string filename);
|
||||
|
||||
std::string Char(std::int32_t code)
|
||||
{
|
||||
auto it = m_chars.find(code);
|
||||
|
||||
if (it == m_chars.end())
|
||||
return std::string();
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::string Escape(unsigned char code)
|
||||
{
|
||||
return m_escapes[code];
|
||||
}
|
||||
|
||||
std::string Constant(std::string identifier)
|
||||
{
|
||||
auto it = m_constants.find(identifier);
|
||||
|
||||
if (it == m_constants.end())
|
||||
return std::string();
|
||||
|
||||
return it->second;
|
||||
}
|
||||
private:
|
||||
std::map<std::int32_t, std::string> m_chars;
|
||||
std::string m_escapes[128];
|
||||
std::map<std::string, std::string> m_constants;
|
||||
};
|
||||
|
||||
#endif // CHARMAP_H
|
156
tools/preproc/preproc.cpp
Normal file
156
tools/preproc/preproc.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright(c) 2016 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 <string>
|
||||
#include <stack>
|
||||
#include "preproc.h"
|
||||
#include "asm_file.h"
|
||||
#include "c_file.h"
|
||||
#include "charmap.h"
|
||||
|
||||
Charmap* g_charmap;
|
||||
|
||||
void PrintAsmBytes(unsigned char *s, int length)
|
||||
{
|
||||
if (length > 0)
|
||||
{
|
||||
std::printf("\t.byte ");
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
std::printf("0x%02X", s[i]);
|
||||
|
||||
if (i < length - 1)
|
||||
std::printf(", ");
|
||||
}
|
||||
std::putchar('\n');
|
||||
}
|
||||
}
|
||||
|
||||
void PreprocAsmFile(std::string filename)
|
||||
{
|
||||
std::stack<AsmFile> stack;
|
||||
|
||||
stack.push(AsmFile(filename));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
while (stack.top().IsAtEnd())
|
||||
{
|
||||
stack.pop();
|
||||
|
||||
if (stack.empty())
|
||||
return;
|
||||
else
|
||||
stack.top().OutputLocation();
|
||||
}
|
||||
|
||||
Directive directive = stack.top().GetDirective();
|
||||
|
||||
switch (directive)
|
||||
{
|
||||
case Directive::Include:
|
||||
stack.push(AsmFile(stack.top().ReadPath()));
|
||||
stack.top().OutputLocation();
|
||||
break;
|
||||
case Directive::String:
|
||||
{
|
||||
unsigned char s[kMaxStringLength];
|
||||
int length = stack.top().ReadString(s);
|
||||
PrintAsmBytes(s, length);
|
||||
break;
|
||||
}
|
||||
case Directive::Braille:
|
||||
{
|
||||
unsigned char s[kMaxStringLength];
|
||||
int length = stack.top().ReadBraille(s);
|
||||
PrintAsmBytes(s, length);
|
||||
break;
|
||||
}
|
||||
case Directive::Unknown:
|
||||
{
|
||||
std::string globalLabel = stack.top().GetGlobalLabel();
|
||||
|
||||
if (globalLabel.length() != 0)
|
||||
{
|
||||
const char *s = globalLabel.c_str();
|
||||
std::printf("%s: ; .global %s\n", s, s);
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.top().OutputLine();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PreprocCFile(std::string filename)
|
||||
{
|
||||
CFile cFile(filename);
|
||||
cFile.Preproc();
|
||||
}
|
||||
|
||||
char* GetFileExtension(char* filename)
|
||||
{
|
||||
char* extension = filename;
|
||||
|
||||
while (*extension != 0)
|
||||
extension++;
|
||||
|
||||
while (extension > filename && *extension != '.')
|
||||
extension--;
|
||||
|
||||
if (extension == filename)
|
||||
return nullptr;
|
||||
|
||||
extension++;
|
||||
|
||||
if (*extension == 0)
|
||||
return nullptr;
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
std::fprintf(stderr, "Usage: %s SRC_FILE CHARMAP_FILE", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_charmap = new Charmap(argv[2]);
|
||||
|
||||
char* extension = GetFileExtension(argv[1]);
|
||||
|
||||
if (!extension)
|
||||
FATAL_ERROR("\"%s\" has no file extension.\n", argv[1]);
|
||||
|
||||
if ((extension[0] == 's') && extension[1] == 0)
|
||||
PreprocAsmFile(argv[1]);
|
||||
else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0)
|
||||
PreprocCFile(argv[1]);
|
||||
else
|
||||
FATAL_ERROR("\"%s\" has an unknown file extension of \"%s\".\n", argv[1], extension);
|
||||
|
||||
return 0;
|
||||
}
|
54
tools/preproc/preproc.h
Normal file
54
tools/preproc/preproc.h
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright(c) 2016 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 PREPROC_H
|
||||
#define PREPROC_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include "charmap.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
std::fprintf(stderr, format, __VA_ARGS__); \
|
||||
std::exit(1); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
std::fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
std::exit(1); \
|
||||
} while (0)
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
const int kMaxPath = 256;
|
||||
const int kMaxStringLength = 1024;
|
||||
const unsigned long kMaxCharmapSequenceLength = 16;
|
||||
|
||||
extern Charmap* g_charmap;
|
||||
|
||||
#endif // PREPROC_H
|
355
tools/preproc/string_parser.cpp
Normal file
355
tools/preproc/string_parser.cpp
Normal file
@ -0,0 +1,355 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include <stdexcept>
|
||||
#include "preproc.h"
|
||||
#include "string_parser.h"
|
||||
#include "char_util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
// Reads a charmap char or escape sequence.
|
||||
std::string StringParser::ReadCharOrEscape()
|
||||
{
|
||||
std::string sequence;
|
||||
|
||||
bool isEscape = (m_buffer[m_pos] == '\\');
|
||||
|
||||
if (isEscape)
|
||||
{
|
||||
m_pos++;
|
||||
|
||||
if (m_buffer[m_pos] == '"')
|
||||
{
|
||||
sequence = g_charmap->Char('"');
|
||||
|
||||
if (sequence.length() == 0)
|
||||
RaiseError("no mapping exists for double quote");
|
||||
|
||||
return sequence;
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\\')
|
||||
{
|
||||
sequence = g_charmap->Char('\\');
|
||||
|
||||
if (sequence.length() == 0)
|
||||
RaiseError("no mapping exists for backslash");
|
||||
|
||||
return sequence;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char c = m_buffer[m_pos];
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseError("unexpected EOF in UTF-8 string");
|
||||
else
|
||||
RaiseError("unexpected null character in UTF-8 string");
|
||||
}
|
||||
|
||||
if (IsAscii(c) && !IsAsciiPrintable(c))
|
||||
RaiseError("unexpected character U+%X in UTF-8 string", c);
|
||||
|
||||
UnicodeChar unicodeChar = DecodeUtf8(&m_buffer[m_pos]);
|
||||
m_pos += unicodeChar.encodingLength;
|
||||
std::int32_t code = unicodeChar.code;
|
||||
|
||||
if (code == -1)
|
||||
RaiseError("invalid encoding in UTF-8 string");
|
||||
|
||||
if (isEscape && code >= 128)
|
||||
RaiseError("escapes using non-ASCII characters are invalid");
|
||||
|
||||
sequence = isEscape ? g_charmap->Escape(code) : g_charmap->Char(code);
|
||||
|
||||
if (sequence.length() == 0)
|
||||
{
|
||||
if (isEscape)
|
||||
RaiseError("unknown escape '\\%c'", code);
|
||||
else
|
||||
RaiseError("unknown character U+%X", code);
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Reads a charmap constant, i.e. "{FOO}".
|
||||
std::string StringParser::ReadBracketedConstants()
|
||||
{
|
||||
std::string totalSequence;
|
||||
|
||||
m_pos++; // Assume we're on the left curly bracket.
|
||||
|
||||
while (m_buffer[m_pos] != '}')
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (IsIdentifierStartingChar(m_buffer[m_pos]))
|
||||
{
|
||||
long startPos = m_pos;
|
||||
|
||||
m_pos++;
|
||||
|
||||
while (IsIdentifierChar(m_buffer[m_pos]))
|
||||
m_pos++;
|
||||
|
||||
std::string sequence = g_charmap->Constant(std::string(&m_buffer[startPos], m_pos - startPos));
|
||||
|
||||
if (sequence.length() == 0)
|
||||
{
|
||||
m_buffer[m_pos] = 0;
|
||||
RaiseError("unknown constant '%s'", &m_buffer[startPos]);
|
||||
}
|
||||
|
||||
totalSequence += sequence;
|
||||
}
|
||||
else if (IsAsciiDigit(m_buffer[m_pos]))
|
||||
{
|
||||
Integer integer = ReadInteger();
|
||||
|
||||
switch (integer.size)
|
||||
{
|
||||
case 1:
|
||||
totalSequence += (unsigned char)integer.value;
|
||||
break;
|
||||
case 2:
|
||||
totalSequence += (unsigned char)integer.value;
|
||||
totalSequence += (unsigned char)(integer.value >> 8);
|
||||
break;
|
||||
case 4:
|
||||
totalSequence += (unsigned char)integer.value;
|
||||
totalSequence += (unsigned char)(integer.value >> 8);
|
||||
totalSequence += (unsigned char)(integer.value >> 16);
|
||||
totalSequence += (unsigned char)(integer.value >> 24);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (m_buffer[m_pos] == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseError("unexpected EOF after left curly bracket");
|
||||
else
|
||||
RaiseError("unexpected null character within curly brackets");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsAsciiPrintable(m_buffer[m_pos]))
|
||||
RaiseError("unexpected character '%c' within curly brackets", m_buffer[m_pos]);
|
||||
else
|
||||
RaiseError("unexpected character '\\x%02X' within curly brackets", m_buffer[m_pos]);
|
||||
}
|
||||
}
|
||||
|
||||
m_pos++; // Go past the right curly bracket.
|
||||
|
||||
return totalSequence;
|
||||
}
|
||||
|
||||
// Reads a charmap string.
|
||||
int StringParser::ParseString(long srcPos, unsigned char* dest, int& destLength)
|
||||
{
|
||||
m_pos = srcPos;
|
||||
|
||||
if (m_buffer[m_pos] != '"')
|
||||
RaiseError("expected UTF-8 string literal");
|
||||
|
||||
long start = m_pos;
|
||||
|
||||
m_pos++;
|
||||
|
||||
destLength = 0;
|
||||
|
||||
while (m_buffer[m_pos] != '"')
|
||||
{
|
||||
std::string sequence = (m_buffer[m_pos] == '{') ? ReadBracketedConstants() : ReadCharOrEscape();
|
||||
|
||||
for (const char& c : sequence)
|
||||
{
|
||||
if (destLength == kMaxStringLength)
|
||||
RaiseError("mapped string longer than %d bytes", kMaxStringLength);
|
||||
|
||||
dest[destLength++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
m_pos++; // Go past the right quote.
|
||||
|
||||
return m_pos - start;
|
||||
}
|
||||
|
||||
void StringParser::RaiseError(const char* format, ...)
|
||||
{
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
|
||||
std::va_list args;
|
||||
va_start(args, format);
|
||||
std::vsnprintf(buffer, bufferSize, format, args);
|
||||
va_end(args);
|
||||
|
||||
throw std::runtime_error(buffer);
|
||||
}
|
||||
|
||||
// Converts digit character to numerical value.
|
||||
static int ConvertDigit(char c, int radix)
|
||||
{
|
||||
int digit;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
digit = c - '0';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
digit = 10 + c - 'A';
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
digit = 10 + c - 'a';
|
||||
else
|
||||
return -1;
|
||||
|
||||
return (digit < radix) ? digit : -1;
|
||||
}
|
||||
|
||||
void StringParser::SkipRestOfInteger(int radix)
|
||||
{
|
||||
while (ConvertDigit(m_buffer[m_pos], radix) != -1)
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
StringParser::Integer StringParser::ReadDecimal()
|
||||
{
|
||||
const int radix = 10;
|
||||
std::uint64_t n = 0;
|
||||
int digit;
|
||||
std::uint64_t max = UINT32_MAX;
|
||||
long startPos = m_pos;
|
||||
|
||||
while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1)
|
||||
{
|
||||
n = n * radix + digit;
|
||||
|
||||
if (n >= max)
|
||||
{
|
||||
SkipRestOfInteger(radix);
|
||||
|
||||
std::string intLiteral(m_buffer + startPos, m_pos - startPos);
|
||||
RaiseError("integer literal \"%s\" is too large", intLiteral.c_str());
|
||||
}
|
||||
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
int size;
|
||||
|
||||
if (m_buffer[m_pos] == 'H')
|
||||
{
|
||||
if (n >= 0x10000)
|
||||
{
|
||||
RaiseError("%lu is too large to be a halfword", (unsigned long)n);
|
||||
}
|
||||
|
||||
size = 2;
|
||||
m_pos++;
|
||||
}
|
||||
else if (m_buffer[m_pos] == 'W')
|
||||
{
|
||||
size = 4;
|
||||
m_pos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (n >= 0x10000)
|
||||
size = 4;
|
||||
else if (n >= 0x100)
|
||||
size = 2;
|
||||
else
|
||||
size = 1;
|
||||
}
|
||||
|
||||
return{ static_cast<std::uint32_t>(n), size };
|
||||
}
|
||||
|
||||
StringParser::Integer StringParser::ReadHex()
|
||||
{
|
||||
const int radix = 16;
|
||||
std::uint64_t n = 0;
|
||||
int digit;
|
||||
std::uint64_t max = UINT32_MAX;
|
||||
long startPos = m_pos;
|
||||
|
||||
while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1)
|
||||
{
|
||||
n = n * radix + digit;
|
||||
|
||||
if (n >= max)
|
||||
{
|
||||
SkipRestOfInteger(radix);
|
||||
|
||||
std::string intLiteral(m_buffer + startPos, m_pos - startPos);
|
||||
RaiseError("integer literal \"%s\" is too large", intLiteral.c_str());
|
||||
}
|
||||
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
int length = m_pos - startPos;
|
||||
int size = 0;
|
||||
|
||||
switch (length)
|
||||
{
|
||||
case 2:
|
||||
size = 1;
|
||||
break;
|
||||
case 4:
|
||||
size = 2;
|
||||
break;
|
||||
case 8:
|
||||
size = 4;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
std::string intLiteral(m_buffer + startPos, m_pos - startPos);
|
||||
RaiseError("hex integer literal \"0x%s\" doesn't have length of 2, 4, or 8 digits", intLiteral.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return{ static_cast<std::uint32_t>(n), size };
|
||||
}
|
||||
|
||||
StringParser::Integer StringParser::ReadInteger()
|
||||
{
|
||||
if (!IsAsciiDigit(m_buffer[m_pos]))
|
||||
RaiseError("expected integer");
|
||||
|
||||
if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x')
|
||||
{
|
||||
m_pos += 2;
|
||||
return ReadHex();
|
||||
}
|
||||
|
||||
return ReadDecimal();
|
||||
}
|
||||
|
||||
// Skips tabs and spaces.
|
||||
void StringParser::SkipWhitespace()
|
||||
{
|
||||
while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
|
||||
m_pos++;
|
||||
}
|
55
tools/preproc/string_parser.h
Normal file
55
tools/preproc/string_parser.h
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright(c) 2016 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 STRING_PARSER_H
|
||||
#define STRING_PARSER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "preproc.h"
|
||||
|
||||
class StringParser
|
||||
{
|
||||
public:
|
||||
StringParser(char* buffer, long size) : m_buffer(buffer), m_size(size), m_pos(0) {}
|
||||
int ParseString(long srcPos, unsigned char* dest, int &destLength);
|
||||
|
||||
private:
|
||||
struct Integer
|
||||
{
|
||||
std::uint32_t value;
|
||||
int size;
|
||||
};
|
||||
|
||||
char* m_buffer;
|
||||
long m_size;
|
||||
long m_pos;
|
||||
|
||||
Integer ReadInteger();
|
||||
Integer ReadDecimal();
|
||||
Integer ReadHex();
|
||||
std::string ReadCharOrEscape();
|
||||
std::string ReadBracketedConstants();
|
||||
void SkipWhitespace();
|
||||
void SkipRestOfInteger(int radix);
|
||||
void RaiseError(const char* format, ...);
|
||||
};
|
||||
|
||||
#endif // STRING_PARSER_H
|
92
tools/preproc/utf8.cpp
Normal file
92
tools/preproc/utf8.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
//
|
||||
// Copyright(c) 2016 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 <cstdint>
|
||||
#include "utf8.h"
|
||||
|
||||
static const unsigned char s_byteTypeTable[] =
|
||||
{
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
|
||||
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
|
||||
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
|
||||
};
|
||||
|
||||
const unsigned char s0 = 0 * 12;
|
||||
const unsigned char s1 = 1 * 12;
|
||||
const unsigned char s2 = 2 * 12;
|
||||
const unsigned char s3 = 3 * 12;
|
||||
const unsigned char s4 = 4 * 12;
|
||||
const unsigned char s5 = 5 * 12;
|
||||
const unsigned char s6 = 6 * 12;
|
||||
const unsigned char s7 = 7 * 12;
|
||||
const unsigned char s8 = 8 * 12;
|
||||
|
||||
static const unsigned char s_transitionTable[] =
|
||||
{
|
||||
s0,s1,s2,s3,s5,s8,s7,s1,s1,s1,s4,s6, // s0
|
||||
s1,s1,s1,s1,s1,s1,s1,s1,s1,s1,s1,s1, // s1
|
||||
s1,s0,s1,s1,s1,s1,s1,s0,s1,s0,s1,s1, // s2
|
||||
s1,s2,s1,s1,s1,s1,s1,s2,s1,s2,s1,s1, // s3
|
||||
s1,s1,s1,s1,s1,s1,s1,s2,s1,s1,s1,s1, // s4
|
||||
s1,s2,s1,s1,s1,s1,s1,s1,s1,s2,s1,s1, // s5
|
||||
s1,s1,s1,s1,s1,s1,s1,s3,s1,s3,s1,s1, // s6
|
||||
s1,s3,s1,s1,s1,s1,s1,s3,s1,s3,s1,s1, // s7
|
||||
s1,s3,s1,s1,s1,s1,s1,s1,s1,s1,s1,s1, // s8
|
||||
};
|
||||
|
||||
// Decodes UTF-8 encoded Unicode code point at "s".
|
||||
UnicodeChar DecodeUtf8(const char* s)
|
||||
{
|
||||
UnicodeChar unicodeChar;
|
||||
int state = s0;
|
||||
auto start = s;
|
||||
|
||||
do
|
||||
{
|
||||
unsigned char byte = *s++;
|
||||
int type = s_byteTypeTable[byte];
|
||||
|
||||
if (state == s0)
|
||||
unicodeChar.code = (0xFF >> type) & byte;
|
||||
else
|
||||
unicodeChar.code = (unicodeChar.code << 6) | (byte & 0x3F);
|
||||
|
||||
state = s_transitionTable[state + type];
|
||||
|
||||
if (state == s1)
|
||||
{
|
||||
unicodeChar.code = -1;
|
||||
return unicodeChar;
|
||||
}
|
||||
} while (state != s0);
|
||||
|
||||
unicodeChar.encodingLength = s - start;
|
||||
|
||||
return unicodeChar;
|
||||
}
|
34
tools/preproc/utf8.h
Normal file
34
tools/preproc/utf8.h
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright(c) 2016 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 UTF8_H
|
||||
#define UTF8_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct UnicodeChar
|
||||
{
|
||||
std::int32_t code;
|
||||
int encodingLength;
|
||||
};
|
||||
|
||||
UnicodeChar DecodeUtf8(const char* s);
|
||||
|
||||
#endif // UTF8_H
|
1
tools/ramscrgen/.gitignore
vendored
Normal file
1
tools/ramscrgen/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
ramscrgen
|
19
tools/ramscrgen/LICENSE
Normal file
19
tools/ramscrgen/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2016 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.
|
15
tools/ramscrgen/Makefile
Normal file
15
tools/ramscrgen/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CXX := g++
|
||||
|
||||
CXXFLAGS := -std=c++11 -O2 -s -Wall -Wno-switch -Werror
|
||||
|
||||
SRCS := main.cpp sym_file.cpp elf.cpp
|
||||
|
||||
HEADERS := ramscrgen.h sym_file.h elf.h char_util.h
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
ramscrgen: $(SRCS) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) ramscrgen ramscrgen.exe
|
71
tools/ramscrgen/char_util.h
Normal file
71
tools/ramscrgen/char_util.h
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright(c) 2016 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 CHAR_UTIL_H
|
||||
#define CHAR_UTIL_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
|
||||
inline bool IsAscii(unsigned char c)
|
||||
{
|
||||
return (c < 128);
|
||||
}
|
||||
|
||||
inline bool IsAsciiAlpha(unsigned char c)
|
||||
{
|
||||
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
||||
}
|
||||
|
||||
inline bool IsAsciiDigit(unsigned char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
inline bool IsAsciiHexDigit(unsigned char c)
|
||||
{
|
||||
return ((c >= '0' && c <= '9')
|
||||
|| (c >= 'a' && c <= 'f')
|
||||
|| (c >= 'A' && c <= 'F'));
|
||||
}
|
||||
|
||||
inline bool IsAsciiAlphanum(unsigned char c)
|
||||
{
|
||||
return (IsAsciiAlpha(c) || IsAsciiDigit(c));
|
||||
}
|
||||
|
||||
inline bool IsAsciiPrintable(unsigned char c)
|
||||
{
|
||||
return (c >= ' ' && c <= '~');
|
||||
}
|
||||
|
||||
// Returns whether the character can start 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.
|
||||
inline bool IsIdentifierChar(unsigned char c)
|
||||
{
|
||||
return IsAsciiAlphanum(c) || c == '_';
|
||||
}
|
||||
|
||||
#endif // CHAR_UTIL_H
|
195
tools/ramscrgen/elf.cpp
Normal file
195
tools/ramscrgen/elf.cpp
Normal file
@ -0,0 +1,195 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "ramscrgen.h"
|
||||
#include "elf.h"
|
||||
|
||||
#define SHN_COMMON 0xFFF2
|
||||
|
||||
static std::string s_elfPath;
|
||||
|
||||
static FILE *s_file;
|
||||
|
||||
static std::uint32_t s_sectionHeaderOffset;
|
||||
static int s_sectionHeaderEntrySize;
|
||||
static int s_sectionCount;
|
||||
static int s_shstrtabIndex;
|
||||
|
||||
static std::uint32_t s_symtabOffset;
|
||||
static std::uint32_t s_strtabOffset;
|
||||
|
||||
static std::uint32_t s_symbolCount;
|
||||
|
||||
struct Symbol
|
||||
{
|
||||
std::uint32_t nameOffset;
|
||||
std::uint32_t size;
|
||||
};
|
||||
|
||||
static void Seek(long offset)
|
||||
{
|
||||
if (std::fseek(s_file, offset, SEEK_SET) != 0)
|
||||
FATAL_ERROR("error: failed to seek to %ld in \"%s\"", offset, s_elfPath.c_str());
|
||||
}
|
||||
|
||||
static void Skip(long offset)
|
||||
{
|
||||
if (std::fseek(s_file, offset, SEEK_CUR) != 0)
|
||||
FATAL_ERROR("error: failed to skip %ld bytes in \"%s\"", offset, s_elfPath.c_str());
|
||||
}
|
||||
|
||||
static std::uint32_t ReadInt8()
|
||||
{
|
||||
int c = std::fgetc(s_file);
|
||||
|
||||
if (c < 0)
|
||||
FATAL_ERROR("error: unexpected EOF when reading ELF file \"%s\"\n", s_elfPath.c_str());
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static std::uint32_t ReadInt16()
|
||||
{
|
||||
std::uint32_t val = 0;
|
||||
val |= ReadInt8();
|
||||
val |= ReadInt8() << 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
static std::uint32_t ReadInt32()
|
||||
{
|
||||
std::uint32_t val = 0;
|
||||
val |= ReadInt8();
|
||||
val |= ReadInt8() << 8;
|
||||
val |= ReadInt8() << 16;
|
||||
val |= ReadInt8() << 24;
|
||||
return val;
|
||||
}
|
||||
|
||||
static std::string ReadString()
|
||||
{
|
||||
std::string s;
|
||||
char c;
|
||||
|
||||
while ((c = ReadInt8()) != 0)
|
||||
s += c;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void VerifyElfIdent()
|
||||
{
|
||||
char expectedMagic[4] = { 0x7F, 'E', 'L', 'F' };
|
||||
char magic[4];
|
||||
|
||||
if (std::fread(magic, 4, 1, s_file) != 1)
|
||||
FATAL_ERROR("error: failed to read ELF magic from \"%s\"\n", s_elfPath.c_str());
|
||||
|
||||
if (std::memcmp(magic, expectedMagic, 4) != 0)
|
||||
FATAL_ERROR("error: ELF magic did not match in \"%s\"\n", s_elfPath.c_str());
|
||||
|
||||
if (std::fgetc(s_file) != 1)
|
||||
FATAL_ERROR("error: \"%s\" not 32-bit ELF\n", s_elfPath.c_str());
|
||||
|
||||
if (std::fgetc(s_file) != 1)
|
||||
FATAL_ERROR("error: \"%s\" not little-endian ELF\n", s_elfPath.c_str());
|
||||
}
|
||||
|
||||
static void ReadElfHeader()
|
||||
{
|
||||
Seek(0x20);
|
||||
s_sectionHeaderOffset = ReadInt32();
|
||||
Seek(0x2E);
|
||||
s_sectionHeaderEntrySize = ReadInt16();
|
||||
s_sectionCount = ReadInt16();
|
||||
s_shstrtabIndex = ReadInt16();
|
||||
}
|
||||
|
||||
static std::string GetSectionName(std::uint32_t shstrtabOffset, int index)
|
||||
{
|
||||
Seek(s_sectionHeaderOffset + s_sectionHeaderEntrySize * index);
|
||||
std::uint32_t nameOffset = ReadInt32();
|
||||
Seek(shstrtabOffset + nameOffset);
|
||||
return ReadString();
|
||||
}
|
||||
|
||||
static void FindTableOffsets()
|
||||
{
|
||||
s_symtabOffset = 0;
|
||||
s_strtabOffset = 0;
|
||||
|
||||
Seek(s_sectionHeaderOffset + s_sectionHeaderEntrySize * s_shstrtabIndex + 0x10);
|
||||
std::uint32_t shstrtabOffset = ReadInt32();
|
||||
|
||||
for (int i = 0; i < s_sectionCount; i++)
|
||||
{
|
||||
std::string name = GetSectionName(shstrtabOffset, i);
|
||||
|
||||
if (name == ".symtab")
|
||||
{
|
||||
if (s_symtabOffset)
|
||||
FATAL_ERROR("error: mutiple .symtab sections found in \"%s\"\n", s_elfPath.c_str());
|
||||
Seek(s_sectionHeaderOffset + s_sectionHeaderEntrySize * i + 0x10);
|
||||
s_symtabOffset = ReadInt32();
|
||||
std::uint32_t size = ReadInt32();
|
||||
s_symbolCount = size / 16;
|
||||
}
|
||||
else if (name == ".strtab")
|
||||
{
|
||||
if (s_strtabOffset)
|
||||
FATAL_ERROR("error: mutiple .strtab sections found in \"%s\"\n", s_elfPath.c_str());
|
||||
Seek(s_sectionHeaderOffset + s_sectionHeaderEntrySize * i + 0x10);
|
||||
s_strtabOffset = ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
if (!s_symtabOffset)
|
||||
FATAL_ERROR("error: couldn't find .symtab section in \"%s\"\n", s_elfPath.c_str());
|
||||
|
||||
if (!s_strtabOffset)
|
||||
FATAL_ERROR("error: couldn't find .strtab section in \"%s\"\n", s_elfPath.c_str());
|
||||
}
|
||||
|
||||
std::map<std::string, std::uint32_t> GetCommonSymbols(std::string path)
|
||||
{
|
||||
s_elfPath = path;
|
||||
|
||||
std::map<std::string, std::uint32_t> commonSymbols;
|
||||
|
||||
s_file = std::fopen(s_elfPath.c_str(), "rb");
|
||||
|
||||
if (s_file == NULL)
|
||||
FATAL_ERROR("error: failed to open \"%s\" for reading\n", path.c_str());
|
||||
|
||||
VerifyElfIdent();
|
||||
ReadElfHeader();
|
||||
FindTableOffsets();
|
||||
|
||||
std::vector<Symbol> commonSymbolVec;
|
||||
|
||||
Seek(s_symtabOffset);
|
||||
|
||||
for (std::uint32_t i = 0; i < s_symbolCount; i++)
|
||||
{
|
||||
Symbol sym;
|
||||
sym.nameOffset = ReadInt32();
|
||||
Skip(4);
|
||||
sym.size = ReadInt32();
|
||||
Skip(2);
|
||||
std::uint16_t sectionIndex = ReadInt16();
|
||||
if (sectionIndex == SHN_COMMON)
|
||||
commonSymbolVec.push_back(sym);
|
||||
}
|
||||
|
||||
for (const Symbol& sym : commonSymbolVec)
|
||||
{
|
||||
Seek(s_strtabOffset + sym.nameOffset);
|
||||
std::string name = ReadString();
|
||||
commonSymbols[name] = sym.size;
|
||||
}
|
||||
|
||||
return commonSymbols;
|
||||
}
|
30
tools/ramscrgen/elf.h
Normal file
30
tools/ramscrgen/elf.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright(c) 2016 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 ELF_H
|
||||
#define ELF_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
std::map<std::string, std::uint32_t> GetCommonSymbols(std::string path);
|
||||
|
||||
#endif // ELF_H
|
173
tools/ramscrgen/main.cpp
Normal file
173
tools/ramscrgen/main.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright(c) 2016 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 <cstring>
|
||||
#include <string>
|
||||
#include "ramscrgen.h"
|
||||
#include "sym_file.h"
|
||||
#include "elf.h"
|
||||
|
||||
void HandleCommonInclude(std::string filename, std::string sourcePath, std::string symOrderPath, std::string lang)
|
||||
{
|
||||
auto commonSymbols = GetCommonSymbols(sourcePath + "/" + filename);
|
||||
|
||||
std::size_t dotIndex = filename.find_last_of('.');
|
||||
|
||||
if (dotIndex == std::string::npos)
|
||||
FATAL_ERROR("error: \"%s\" doesn't have a file extension\n", filename.c_str());
|
||||
|
||||
std::string symOrderFilename = filename.substr(0, dotIndex + 1) + "txt";
|
||||
|
||||
SymFile symFile(symOrderPath + "/" + symOrderFilename);
|
||||
|
||||
while (!symFile.IsAtEnd())
|
||||
{
|
||||
symFile.HandleLangConditional(lang);
|
||||
|
||||
std::string label = symFile.GetLabel(false);
|
||||
|
||||
if (label.length() == 0)
|
||||
{
|
||||
unsigned long length;
|
||||
if (symFile.ReadInteger(length))
|
||||
{
|
||||
if (length & 3)
|
||||
symFile.RaiseWarning("gap length %d is not multiple of 4", length);
|
||||
printf(". += 0x%lX;\n", length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (commonSymbols.count(label) == 0)
|
||||
symFile.RaiseError("no common symbol named \"%s\"", label.c_str());
|
||||
unsigned long size = commonSymbols[label];
|
||||
int alignment = 4;
|
||||
if (size > 4)
|
||||
alignment = 8;
|
||||
if (size > 8)
|
||||
alignment = 16;
|
||||
printf(". = ALIGN(%d);\n", alignment);
|
||||
printf("%s = .;\n", label.c_str());
|
||||
printf(". += 0x%lX;\n", size);
|
||||
}
|
||||
|
||||
symFile.ExpectEmptyRestOfLine();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
case Directive::Include:
|
||||
{
|
||||
std::string incFilename = symFile.ReadPath();
|
||||
symFile.ExpectEmptyRestOfLine();
|
||||
printf(". = ALIGN(4);\n");
|
||||
if (common)
|
||||
HandleCommonInclude(incFilename, sourcePath, commonSymPath, lang);
|
||||
else
|
||||
printf("%s(%s);\n", incFilename.c_str(), sectionName.c_str());
|
||||
break;
|
||||
}
|
||||
case Directive::Space:
|
||||
{
|
||||
unsigned long length;
|
||||
if (!symFile.ReadInteger(length))
|
||||
symFile.RaiseError("expected integer after .space directive");
|
||||
symFile.ExpectEmptyRestOfLine();
|
||||
printf(". += 0x%lX;\n", length);
|
||||
break;
|
||||
}
|
||||
case Directive::Align:
|
||||
{
|
||||
unsigned long amount;
|
||||
if (!symFile.ReadInteger(amount))
|
||||
symFile.RaiseError("expected integer after .align directive");
|
||||
if (amount > 4)
|
||||
symFile.RaiseError("max alignment amount is 4");
|
||||
amount = 1UL << amount;
|
||||
symFile.ExpectEmptyRestOfLine();
|
||||
printf(". = ALIGN(%lu);\n", amount);
|
||||
break;
|
||||
}
|
||||
case Directive::Unknown:
|
||||
{
|
||||
std::string label = symFile.GetLabel();
|
||||
|
||||
if (label.length() != 0)
|
||||
{
|
||||
printf("%s = .;\n", label.c_str());
|
||||
}
|
||||
|
||||
symFile.ExpectEmptyRestOfLine();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 4)
|
||||
{
|
||||
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 > 4)
|
||||
{
|
||||
if (std::strcmp(argv[4], "-c") != 0)
|
||||
FATAL_ERROR("error: unrecognized argument \"%s\"\n", argv[4]);
|
||||
|
||||
if (argc < 6)
|
||||
FATAL_ERROR("error: missing SRC_PATH,COMMON_SYM_PATH after \"-c\"\n");
|
||||
|
||||
common = true;
|
||||
std::string paths = std::string(argv[5]);
|
||||
std::size_t commaPos = paths.find(',');
|
||||
|
||||
if (commaPos == std::string::npos)
|
||||
FATAL_ERROR("error: missing comma in argument after \"-c\"\n");
|
||||
|
||||
sourcePath = paths.substr(0, commaPos);
|
||||
commonSymPath = paths.substr(commaPos + 1);
|
||||
}
|
||||
|
||||
ConvertSymFile(symFileName, sectionName, lang, common, sourcePath, commonSymPath);
|
||||
return 0;
|
||||
}
|
49
tools/ramscrgen/ramscrgen.h
Normal file
49
tools/ramscrgen/ramscrgen.h
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright(c) 2016 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 RAMSCRGEN_H
|
||||
#define RAMSCRGEN_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
std::fprintf(stderr, format, __VA_ARGS__); \
|
||||
std::exit(1); \
|
||||
} while (0)
|
||||
|
||||
#else
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do \
|
||||
{ \
|
||||
std::fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
std::exit(1); \
|
||||
} while (0)
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
const int kMaxPath = 256;
|
||||
|
||||
#endif // RAMSCRGEN_H
|
492
tools/ramscrgen/sym_file.cpp
Normal file
492
tools/ramscrgen/sym_file.cpp
Normal file
@ -0,0 +1,492 @@
|
||||
// Copyright(c) 2016 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 <cstdarg>
|
||||
#include <climits>
|
||||
#include "ramscrgen.h"
|
||||
#include "sym_file.h"
|
||||
#include "char_util.h"
|
||||
|
||||
SymFile::SymFile(std::string filename) : m_filename(filename)
|
||||
{
|
||||
FILE *fp = std::fopen(filename.c_str(), "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", filename.c_str());
|
||||
|
||||
std::fseek(fp, 0, SEEK_END);
|
||||
|
||||
m_size = std::ftell(fp);
|
||||
|
||||
if (m_size < 0)
|
||||
FATAL_ERROR("File size of \"%s\" is less than zero.\n", filename.c_str());
|
||||
|
||||
m_buffer = new char[m_size + 1];
|
||||
|
||||
std::rewind(fp);
|
||||
|
||||
if (std::fread(m_buffer, m_size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", filename.c_str());
|
||||
|
||||
m_buffer[m_size] = 0;
|
||||
|
||||
std::fclose(fp);
|
||||
|
||||
m_pos = 0;
|
||||
m_lineNum = 1;
|
||||
m_lineStart = 0;
|
||||
m_inLangConditional = false;
|
||||
|
||||
RemoveComments();
|
||||
}
|
||||
|
||||
SymFile::SymFile(SymFile&& other) : m_filename(std::move(other.m_filename))
|
||||
{
|
||||
m_buffer = other.m_buffer;
|
||||
m_pos = other.m_pos;
|
||||
m_size = other.m_size;
|
||||
m_lineNum = other.m_lineNum;
|
||||
m_lineStart = other.m_lineStart;
|
||||
|
||||
other.m_buffer = nullptr;
|
||||
}
|
||||
|
||||
SymFile::~SymFile()
|
||||
{
|
||||
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 SymFile::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] == '@' && (pos == 0 || 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++] = ' ';
|
||||
|
||||
char commentStringChar = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (m_buffer[pos] == 0)
|
||||
return;
|
||||
|
||||
if (commentStringChar != 0)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
}
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_buffer[pos] == '"' || m_buffer[pos] == '\'')
|
||||
stringChar = m_buffer[pos];
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if we're at a particular directive and if so, consumes it.
|
||||
// Returns whether the directive was found.
|
||||
bool SymFile::CheckForDirective(std::string name)
|
||||
{
|
||||
long i;
|
||||
long length = static_cast<long>(name.length());
|
||||
|
||||
for (i = 0; i < length && m_pos + i < m_size; i++)
|
||||
if (name[i] != m_buffer[m_pos + i])
|
||||
return false;
|
||||
|
||||
if (i < length)
|
||||
return false;
|
||||
|
||||
m_pos += length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if we're at a known directive and if so, consumes it.
|
||||
// Returns which directive was found.
|
||||
Directive SymFile::GetDirective()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (CheckForDirective(".include"))
|
||||
return Directive::Include;
|
||||
else if (CheckForDirective(".space"))
|
||||
return Directive::Space;
|
||||
else if (CheckForDirective(".align"))
|
||||
return Directive::Align;
|
||||
else
|
||||
return Directive::Unknown;
|
||||
}
|
||||
|
||||
// Checks if we're at label.
|
||||
// Returns the name if so and an empty string if not.
|
||||
std::string SymFile::GetLabel(bool requireColon)
|
||||
{
|
||||
long start = m_pos;
|
||||
long pos = m_pos;
|
||||
|
||||
if (IsIdentifierStartingChar(m_buffer[pos]))
|
||||
{
|
||||
pos++;
|
||||
|
||||
while (IsIdentifierChar(m_buffer[pos]))
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (requireColon)
|
||||
{
|
||||
if (m_buffer[pos] == ':')
|
||||
{
|
||||
if (pos != start)
|
||||
m_pos = pos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = start;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pos = pos;
|
||||
}
|
||||
|
||||
return std::string(&m_buffer[start], pos - start);
|
||||
}
|
||||
|
||||
// Skips tabs and spaces.
|
||||
void SymFile::SkipWhitespace()
|
||||
{
|
||||
while (m_buffer[m_pos] == '\t' || m_buffer[m_pos] == ' ')
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
// Reads include path.
|
||||
std::string SymFile::ReadPath()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] != '"')
|
||||
RaiseError("expected file path");
|
||||
|
||||
m_pos++;
|
||||
|
||||
int length = 0;
|
||||
long startPos = m_pos;
|
||||
|
||||
while (m_buffer[m_pos] != '"')
|
||||
{
|
||||
unsigned char c = m_buffer[m_pos++];
|
||||
|
||||
if (c == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseError("unexpected EOF in include string");
|
||||
else
|
||||
RaiseError("unexpected null character in include string");
|
||||
}
|
||||
|
||||
if (!IsAsciiPrintable(c))
|
||||
RaiseError("unexpected character '\\x%02X' in include string", c);
|
||||
|
||||
// Don't bother allowing any escape sequences.
|
||||
if (c == '\\')
|
||||
{
|
||||
c = m_buffer[m_pos];
|
||||
RaiseError("unexpected escape '\\%c' in include string", c);
|
||||
}
|
||||
|
||||
length++;
|
||||
|
||||
if (length > kMaxPath)
|
||||
RaiseError("path is too long");
|
||||
}
|
||||
|
||||
m_pos++; // Go past the right quote.
|
||||
|
||||
return std::string(&m_buffer[startPos], length);
|
||||
}
|
||||
|
||||
// If we're at a comma, consumes it.
|
||||
// Returns whether a comma was found.
|
||||
bool SymFile::ConsumeComma()
|
||||
{
|
||||
if (m_buffer[m_pos] == ',')
|
||||
{
|
||||
m_pos++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts digit character to numerical value.
|
||||
static int ConvertDigit(char c, int radix)
|
||||
{
|
||||
int digit;
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
digit = c - '0';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
digit = 10 + c - 'A';
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
digit = 10 + c - 'a';
|
||||
else
|
||||
return -1;
|
||||
|
||||
return (digit < radix) ? digit : -1;
|
||||
}
|
||||
|
||||
// Reads an integer.
|
||||
bool SymFile::ReadInteger(unsigned long& n)
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (!IsAsciiDigit(m_buffer[m_pos]))
|
||||
return false;
|
||||
|
||||
int startPos = m_pos;
|
||||
int radix = 10;
|
||||
|
||||
if (m_buffer[m_pos] == '0' && m_buffer[m_pos + 1] == 'x')
|
||||
{
|
||||
radix = 16;
|
||||
m_pos += 2;
|
||||
}
|
||||
|
||||
unsigned long cutoff = ULONG_MAX / radix;
|
||||
unsigned long cutoffRemainder = ULONG_MAX % radix;
|
||||
int digit;
|
||||
|
||||
n = 0;
|
||||
|
||||
while ((digit = ConvertDigit(m_buffer[m_pos], radix)) != -1)
|
||||
{
|
||||
if (n < cutoff || (n == cutoff && (unsigned long)digit <= cutoffRemainder))
|
||||
{
|
||||
n = n * radix + digit;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pos++;
|
||||
|
||||
while (ConvertDigit(m_buffer[m_pos], radix) != -1)
|
||||
m_pos++;
|
||||
|
||||
RaiseError("integer is too large (%s)", std::string(&m_buffer[startPos], m_pos - startPos).c_str());
|
||||
}
|
||||
|
||||
m_pos++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Asserts that the rest of the line is empty and moves to the next one.
|
||||
void SymFile::ExpectEmptyRestOfLine()
|
||||
{
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] == 0)
|
||||
{
|
||||
if (m_pos >= m_size)
|
||||
RaiseWarning("file doesn't end with newline");
|
||||
else
|
||||
RaiseError("unexpected null character");
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\n')
|
||||
{
|
||||
m_pos++;
|
||||
m_lineStart = m_pos;
|
||||
m_lineNum++;
|
||||
}
|
||||
else if (m_buffer[m_pos] == '\r')
|
||||
{
|
||||
RaiseError("only Unix-style LF newlines are supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
RaiseError("junk at end of line");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
std::vsnprintf(buffer, bufferSize, format, args);
|
||||
std::fprintf(stderr, "%s:%ld: %s: %s\n", m_filename.c_str(), m_lineNum, type, buffer);
|
||||
}
|
||||
|
||||
#define DO_REPORT(type) \
|
||||
do \
|
||||
{ \
|
||||
std::va_list args; \
|
||||
va_start(args, format); \
|
||||
ReportDiagnostic(type, format, args); \
|
||||
va_end(args); \
|
||||
} while (0)
|
||||
|
||||
// Reports an error diagnostic and terminates the program.
|
||||
void SymFile::RaiseError(const char* format, ...)
|
||||
{
|
||||
DO_REPORT("error");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
// Reports a warning diagnostic.
|
||||
void SymFile::RaiseWarning(const char* format, ...)
|
||||
{
|
||||
DO_REPORT("warning");
|
||||
}
|
71
tools/ramscrgen/sym_file.h
Normal file
71
tools/ramscrgen/sym_file.h
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright(c) 2016 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 SYM_FILE_H
|
||||
#define SYM_FILE_H
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include "ramscrgen.h"
|
||||
|
||||
enum class Directive
|
||||
{
|
||||
Include,
|
||||
Space,
|
||||
Align,
|
||||
Unknown
|
||||
};
|
||||
|
||||
class SymFile
|
||||
{
|
||||
public:
|
||||
SymFile(std::string filename);
|
||||
SymFile(SymFile&& other);
|
||||
SymFile(const SymFile&) = delete;
|
||||
~SymFile();
|
||||
Directive GetDirective();
|
||||
std::string GetLabel(bool requireColon = true);
|
||||
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, ...);
|
||||
|
||||
private:
|
||||
char* m_buffer;
|
||||
long m_pos;
|
||||
long m_size;
|
||||
long m_lineNum;
|
||||
long m_lineStart;
|
||||
std::string m_filename;
|
||||
bool m_inLangConditional;
|
||||
|
||||
bool ConsumeComma();
|
||||
void RemoveComments();
|
||||
bool CheckForDirective(std::string name);
|
||||
void SkipWhitespace();
|
||||
void ReportDiagnostic(const char* type, const char* format, std::va_list args);
|
||||
};
|
||||
|
||||
#endif // SYM_FILE_H
|
1
tools/rsfont/.gitignore
vendored
Normal file
1
tools/rsfont/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
rsfont
|
19
tools/rsfont/LICENSE
Normal file
19
tools/rsfont/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015-2016 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.
|
15
tools/rsfont/Makefile
Normal file
15
tools/rsfont/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CC = gcc
|
||||
|
||||
CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -s -DPNG_SKIP_SETJMP_CHECK
|
||||
|
||||
LIBS = -lpng -lz
|
||||
|
||||
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 $@ $(LDFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
$(RM) rsfont rsfont.exe
|
169
tools/rsfont/convert_png.c
Normal file
169
tools/rsfont/convert_png.c
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <setjmp.h>
|
||||
#include <png.h>
|
||||
#include "global.h"
|
||||
#include "convert_png.h"
|
||||
#include "gfx.h"
|
||||
|
||||
void ReadPng(char *path, struct Image *image)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
unsigned char sig[8];
|
||||
|
||||
if (fread(sig, 8, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read PNG signature from \"%s\".\n", path);
|
||||
|
||||
if (png_sig_cmp(sig, 0, 8))
|
||||
FATAL_ERROR("\"%s\" does not have a valid PNG signature.\n", path);
|
||||
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
|
||||
if (!png_ptr)
|
||||
FATAL_ERROR("Failed to create PNG read struct.\n");
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
|
||||
if (!info_ptr)
|
||||
FATAL_ERROR("Failed to create PNG info struct.\n");
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Failed to init I/O for reading \"%s\".\n", path);
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
png_set_sig_bytes(png_ptr, 8);
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
int bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
if (bit_depth != image->bitDepth)
|
||||
FATAL_ERROR("\"%s\" has a bit depth of %d, but the expected bit depth is %d.\n", path, bit_depth, image->bitDepth);
|
||||
|
||||
int color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
|
||||
if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_PALETTE)
|
||||
FATAL_ERROR("\"%s\" has an unsupported color type.\n", path);
|
||||
|
||||
// Check if the image has a palette so that we can tell if the colors need to be inverted later.
|
||||
// Don't read the palette because it's not needed for now.
|
||||
image->hasPalette = (color_type == PNG_COLOR_TYPE_PALETTE);
|
||||
|
||||
image->width = png_get_image_width(png_ptr, info_ptr);
|
||||
image->height = png_get_image_height(png_ptr, info_ptr);
|
||||
|
||||
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
||||
|
||||
image->pixels = malloc(image->height * rowbytes);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate pixel buffer.\n");
|
||||
|
||||
png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
|
||||
|
||||
if (row_pointers == NULL)
|
||||
FATAL_ERROR("Failed to allocate row pointers.\n");
|
||||
|
||||
for (int i = 0; i < image->height; i++)
|
||||
row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error reading from \"%s\".\n", path);
|
||||
|
||||
png_read_image(png_ptr, row_pointers);
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
|
||||
free(row_pointers);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void SetPngPalette(png_structp png_ptr, png_infop info_ptr, struct Palette *palette)
|
||||
{
|
||||
png_colorp colors = malloc(palette->numColors * sizeof(png_color));
|
||||
|
||||
if (colors == NULL)
|
||||
FATAL_ERROR("Failed to allocate PNG palette.\n");
|
||||
|
||||
for (int i = 0; i < palette->numColors; i++) {
|
||||
colors[i].red = palette->colors[i].red;
|
||||
colors[i].green = palette->colors[i].green;
|
||||
colors[i].blue = palette->colors[i].blue;
|
||||
}
|
||||
|
||||
png_set_PLTE(png_ptr, info_ptr, colors, palette->numColors);
|
||||
|
||||
free(colors);
|
||||
}
|
||||
|
||||
void WritePng(char *path, struct Image *image)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
|
||||
|
||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
|
||||
if (!png_ptr)
|
||||
FATAL_ERROR("Failed to create PNG write struct.\n");
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
|
||||
if (!info_ptr)
|
||||
FATAL_ERROR("Failed to create PNG info struct.\n");
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Failed to init I/O for writing \"%s\".\n", path);
|
||||
|
||||
png_init_io(png_ptr, fp);
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error writing header for \"%s\".\n", path);
|
||||
|
||||
int color_type = image->hasPalette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY;
|
||||
|
||||
png_set_IHDR(png_ptr, info_ptr, image->width, image->height,
|
||||
image->bitDepth, color_type, PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||
|
||||
if (image->hasPalette) {
|
||||
SetPngPalette(png_ptr, info_ptr, &image->palette);
|
||||
|
||||
if (image->hasTransparency) {
|
||||
png_byte trans = 0;
|
||||
png_set_tRNS(png_ptr, info_ptr, &trans, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
png_bytepp row_pointers = malloc(image->height * sizeof(png_bytep));
|
||||
|
||||
if (row_pointers == NULL)
|
||||
FATAL_ERROR("Failed to allocate row pointers.\n");
|
||||
|
||||
int rowbytes = png_get_rowbytes(png_ptr, info_ptr);
|
||||
|
||||
for (int i = 0; i < image->height; i++)
|
||||
row_pointers[i] = (png_bytep)(image->pixels + (i * rowbytes));
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error writing \"%s\".\n", path);
|
||||
|
||||
png_write_image(png_ptr, row_pointers);
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr)))
|
||||
FATAL_ERROR("Error ending write of \"%s\".\n", path);
|
||||
|
||||
png_write_end(png_ptr, NULL);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
free(row_pointers);
|
||||
}
|
11
tools/rsfont/convert_png.h
Normal file
11
tools/rsfont/convert_png.h
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef CONVERT_PNG_H
|
||||
#define CONVERT_PNG_H
|
||||
|
||||
#include "gfx.h"
|
||||
|
||||
void ReadPng(char *path, struct Image *image);
|
||||
void WritePng(char *path, struct Image *image);
|
||||
|
||||
#endif // CONVERT_PNG_H
|
455
tools/rsfont/font.c
Normal file
455
tools/rsfont/font.c
Normal file
@ -0,0 +1,455 @@
|
||||
// Copyright(c) 2015-2016 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "font.h"
|
||||
#include "gfx.h"
|
||||
#include "util.h"
|
||||
|
||||
unsigned char gFontPalette[][3] =
|
||||
{
|
||||
{0xFF, 0xFF, 0xFF}, // bg (white)
|
||||
{0x38, 0x38, 0x38}, // fg (dark grey)
|
||||
{0xD8, 0xD8, 0xD8}, // shadow (light grey)
|
||||
};
|
||||
|
||||
void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
|
||||
{
|
||||
for (int glyph = 0; glyph < numGlyphs; glyph++)
|
||||
{
|
||||
if (layout == 0)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t srcRow = src[(glyph * 8) + i];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 8) + i;
|
||||
dest[(y * 128) + x] = (srcRow >> (7 - j)) & 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// layout type 1
|
||||
|
||||
int tile1Offset = glyph * 16;
|
||||
int tile2Offset = tile1Offset + 8;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t srcRow = src[tile1Offset + i];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + i;
|
||||
dest[(y * 128) + x] = (srcRow >> (7 - j)) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t srcRow = src[tile2Offset + i];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + 8 + i;
|
||||
dest[(y * 128) + x] = (srcRow >> (7 - j)) & 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
|
||||
{
|
||||
for (int glyph = 0; glyph < numGlyphs; glyph++)
|
||||
{
|
||||
if (layout == 0)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t destRow = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 8) + i;
|
||||
unsigned char color = src[(y * 128) + x];
|
||||
|
||||
if (color > 1)
|
||||
FATAL_ERROR("More than 2 colors in 1 BPP font.\n");
|
||||
|
||||
destRow <<= 1;
|
||||
destRow |= color;
|
||||
}
|
||||
|
||||
dest[(glyph * 8) + i] = destRow;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// layout type 1
|
||||
|
||||
int tile1Offset = glyph * 16;
|
||||
int tile2Offset = tile1Offset + 8;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t destRow = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + i;
|
||||
unsigned char color = src[(y * 128) + x];
|
||||
|
||||
if (color > 1)
|
||||
FATAL_ERROR("More than 2 colors in 1 BPP font.\n");
|
||||
|
||||
destRow <<= 1;
|
||||
destRow |= color;
|
||||
}
|
||||
|
||||
dest[tile1Offset + i] = destRow;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint8_t destRow = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + 8 + i;
|
||||
unsigned char color = src[(y * 128) + x];
|
||||
|
||||
if (color > 1)
|
||||
FATAL_ERROR("More than 2 colors in 1 BPP font.\n");
|
||||
|
||||
destRow <<= 1;
|
||||
destRow |= color;
|
||||
}
|
||||
|
||||
dest[tile2Offset + i] = destRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
|
||||
{
|
||||
static unsigned char table[16] =
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1,
|
||||
};
|
||||
|
||||
for (int glyph = 0; glyph < numGlyphs; glyph++)
|
||||
{
|
||||
if (layout == 0)
|
||||
{
|
||||
int offset = glyph * 32;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t srcRow = (src[offset + 3] << 24)
|
||||
| (src[offset + 2] << 16)
|
||||
| (src[offset + 1] << 8)
|
||||
| src[offset];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 8) + i;
|
||||
dest[(y * 128) + x] = table[srcRow & 0xF];
|
||||
srcRow >>= 4;
|
||||
}
|
||||
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int tile1Offset;
|
||||
int tile2Offset;
|
||||
|
||||
if (layout == 1)
|
||||
{
|
||||
tile1Offset = glyph * 64;
|
||||
tile2Offset = tile1Offset + 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
tile1Offset = ((glyph / 16) * 1024) + ((glyph % 16) * 32);
|
||||
tile2Offset = tile1Offset + 512;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t srcRow = (src[tile1Offset + 3] << 24)
|
||||
| (src[tile1Offset + 2] << 16)
|
||||
| (src[tile1Offset + 1] << 8)
|
||||
| src[tile1Offset];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + i;
|
||||
dest[(y * 128) + x] = table[srcRow & 0xF];
|
||||
srcRow >>= 4;
|
||||
}
|
||||
|
||||
tile1Offset += 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t srcRow = (src[tile2Offset + 3] << 24)
|
||||
| (src[tile2Offset + 2] << 16)
|
||||
| (src[tile2Offset + 1] << 8)
|
||||
| src[tile2Offset];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + 8 + i;
|
||||
dest[(y * 128) + x] = table[srcRow & 0xF];
|
||||
srcRow >>= 4;
|
||||
}
|
||||
|
||||
tile2Offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numGlyphs, int layout)
|
||||
{
|
||||
static unsigned char table[3] =
|
||||
{
|
||||
0, 15, 14,
|
||||
};
|
||||
|
||||
for (int glyph = 0; glyph < numGlyphs; glyph++)
|
||||
{
|
||||
if (layout == 0)
|
||||
{
|
||||
int offset = glyph * 32;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t destRow = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 8) + i;
|
||||
unsigned char color = src[(y * 128) + x];
|
||||
|
||||
if (color > 2)
|
||||
FATAL_ERROR("More than 3 colors in 4 BPP font.\n");
|
||||
|
||||
destRow >>= 4;
|
||||
destRow |= (table[color] << 28);
|
||||
}
|
||||
|
||||
dest[offset] = destRow & 0xFF;
|
||||
dest[offset + 1] = (destRow >> 8) & 0xFF;
|
||||
dest[offset + 2] = (destRow >> 16) & 0xFF;
|
||||
dest[offset + 3] = (destRow >> 24) & 0xFF;
|
||||
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int tile1Offset;
|
||||
int tile2Offset;
|
||||
|
||||
if (layout == 1)
|
||||
{
|
||||
tile1Offset = glyph * 64;
|
||||
tile2Offset = tile1Offset + 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
tile1Offset = ((glyph / 16) * 1024) + ((glyph % 16) * 32);
|
||||
tile2Offset = tile1Offset + 512;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t destRow = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + i;
|
||||
unsigned char color = src[(y * 128) + x];
|
||||
|
||||
if (color > 2)
|
||||
FATAL_ERROR("More than 3 colors in 4 BPP font.\n");
|
||||
|
||||
destRow >>= 4;
|
||||
destRow |= (table[color] << 28);
|
||||
}
|
||||
|
||||
dest[tile1Offset] = destRow & 0xFF;
|
||||
dest[tile1Offset + 1] = (destRow >> 8) & 0xFF;
|
||||
dest[tile1Offset + 2] = (destRow >> 16) & 0xFF;
|
||||
dest[tile1Offset + 3] = (destRow >> 24) & 0xFF;
|
||||
|
||||
tile1Offset += 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uint32_t destRow = 0;
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int x = ((glyph % 16) * 8) + j;
|
||||
int y = ((glyph / 16) * 16) + 8 + i;
|
||||
unsigned char color = src[(y * 128) + x];
|
||||
|
||||
if (color > 2)
|
||||
FATAL_ERROR("More than 3 colors in 4 BPP font.\n");
|
||||
|
||||
destRow >>= 4;
|
||||
destRow |= (table[color] << 28);
|
||||
}
|
||||
|
||||
dest[tile2Offset] = destRow & 0xFF;
|
||||
dest[tile2Offset + 1] = (destRow >> 8) & 0xFF;
|
||||
dest[tile2Offset + 2] = (destRow >> 16) & 0xFF;
|
||||
dest[tile2Offset + 3] = (destRow >> 24) & 0xFF;
|
||||
|
||||
tile2Offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void SetFontPalette(struct Image *image)
|
||||
{
|
||||
image->hasPalette = true;
|
||||
|
||||
image->palette.numColors = 3;
|
||||
|
||||
for (int i = 0; i < image->palette.numColors; i++)
|
||||
{
|
||||
image->palette.colors[i].red = gFontPalette[i][0];
|
||||
image->palette.colors[i].green = gFontPalette[i][1];
|
||||
image->palette.colors[i].blue = gFontPalette[i][2];
|
||||
}
|
||||
|
||||
image->hasTransparency = false;
|
||||
}
|
||||
|
||||
int CalcFileSize(int numGlyphs, int bpp, int layout)
|
||||
{
|
||||
if (layout == 2)
|
||||
{
|
||||
// assume 4 BPP
|
||||
int numFullRows = numGlyphs / 16;
|
||||
int remainder = numGlyphs % 16;
|
||||
int fullRowsSize = numFullRows * 1024;
|
||||
int remainderSize = 0;
|
||||
|
||||
if (remainder != 0)
|
||||
remainderSize = 1024 - (16 - remainder) * 32;
|
||||
|
||||
return fullRowsSize + remainderSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
int tilesPerGlyph = layout > 0 ? 2 : 1;
|
||||
int bytesPerTile = 8 * bpp;
|
||||
return numGlyphs * tilesPerGlyph * bytesPerTile;
|
||||
}
|
||||
}
|
||||
|
||||
void ReadFont(char *path, struct Image *image, int numGlyphs, int bpp, int layout)
|
||||
{
|
||||
int fileSize;
|
||||
unsigned char *buffer = ReadWholeFile(path, &fileSize);
|
||||
|
||||
int expectedFileSize = CalcFileSize(numGlyphs, bpp, layout);
|
||||
|
||||
if (fileSize != expectedFileSize)
|
||||
FATAL_ERROR("The file size is %d but should be %d.\n", fileSize, expectedFileSize);
|
||||
|
||||
int numRows = (numGlyphs + 15) / 16;
|
||||
int rowHeight = layout > 0 ? 16 : 8;
|
||||
|
||||
image->width = 128;
|
||||
image->height = numRows * rowHeight;
|
||||
image->bitDepth = 8;
|
||||
image->pixels = calloc(image->width * image->height, 1);
|
||||
|
||||
if (image->pixels == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
if (bpp == 1)
|
||||
ConvertFromTiles1Bpp(buffer, image->pixels, numGlyphs, layout);
|
||||
else
|
||||
ConvertFromTiles4Bpp(buffer, image->pixels, numGlyphs, layout);
|
||||
|
||||
free(buffer);
|
||||
|
||||
SetFontPalette(image);
|
||||
}
|
||||
|
||||
void WriteFont(char *path, struct Image *image, int numGlyphs, int bpp, int layout)
|
||||
{
|
||||
if (image->width != 128)
|
||||
FATAL_ERROR("The width of the font image (%d) is not 128.\n", image->width);
|
||||
|
||||
int numRows = (numGlyphs + 15) / 16;
|
||||
int rowHeight = layout > 0 ? 16 : 8;
|
||||
int expectedHeight = numRows * rowHeight;
|
||||
|
||||
if (image->height < expectedHeight)
|
||||
FATAL_ERROR("The height of the font image (%d) is less than %d.\n", image->height, expectedHeight);
|
||||
|
||||
int fileSize = CalcFileSize(numGlyphs, bpp, layout);
|
||||
|
||||
unsigned char *buffer = calloc(fileSize, 1);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for font.\n");
|
||||
|
||||
if (bpp == 1)
|
||||
ConvertToTiles1Bpp(image->pixels, buffer, numGlyphs, layout);
|
||||
else
|
||||
ConvertToTiles4Bpp(image->pixels, buffer, numGlyphs, layout);
|
||||
|
||||
WriteWholeFile(path, buffer, fileSize);
|
||||
|
||||
free(buffer);
|
||||
}
|
30
tools/rsfont/font.h
Normal file
30
tools/rsfont/font.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright(c) 2015-2016 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 FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "gfx.h"
|
||||
|
||||
void ReadFont(char *path, struct Image *image, int numGlyphs, int bpp, int layout);
|
||||
void WriteFont(char *path, struct Image *image, int numGlyphs, int bpp, int layout);
|
||||
|
||||
#endif // FONT_H
|
50
tools/rsfont/gfx.h
Normal file
50
tools/rsfont/gfx.h
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright(c) 2015-2016 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 GFX_H
|
||||
#define GFX_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct Color
|
||||
{
|
||||
unsigned char red;
|
||||
unsigned char green;
|
||||
unsigned char blue;
|
||||
};
|
||||
|
||||
struct Palette
|
||||
{
|
||||
struct Color colors[256];
|
||||
int numColors;
|
||||
};
|
||||
|
||||
struct Image {
|
||||
int width;
|
||||
int height;
|
||||
int bitDepth;
|
||||
unsigned char *pixels;
|
||||
bool hasPalette;
|
||||
struct Palette palette;
|
||||
bool hasTransparency;
|
||||
};
|
||||
|
||||
#endif // GFX_H
|
31
tools/rsfont/global.h
Normal file
31
tools/rsfont/global.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef GLOBAL_H
|
||||
#define GLOBAL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do { \
|
||||
fprintf(stderr, format, __VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#define UNUSED
|
||||
|
||||
#else
|
||||
|
||||
#define FATAL_ERROR(format, ...) \
|
||||
do { \
|
||||
fprintf(stderr, format, ##__VA_ARGS__); \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#define UNUSED __attribute__((__unused__))
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
#endif // GLOBAL_H
|
93
tools/rsfont/main.c
Normal file
93
tools/rsfont/main.c
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright(c) 2015-2016 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include "global.h"
|
||||
#include "util.h"
|
||||
#include "gfx.h"
|
||||
#include "convert_png.h"
|
||||
#include "font.h"
|
||||
|
||||
int ExtensionToBpp(const char *extension)
|
||||
{
|
||||
if (!strcmp(extension, "1bpp"))
|
||||
return 1;
|
||||
else if (!strcmp(extension, "4bpp"))
|
||||
return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 5)
|
||||
FATAL_ERROR("Usage: rsfont INPUT_FILE OUTPUT_FILE NUM_GLYPHS LAYOUT_TYPE\n");
|
||||
|
||||
char *inputPath = argv[1];
|
||||
char *outputPath = argv[2];
|
||||
char *inputFileExtension = GetFileExtension(inputPath);
|
||||
char *outputFileExtension = GetFileExtension(outputPath);
|
||||
|
||||
if (inputFileExtension == NULL)
|
||||
FATAL_ERROR("Input file \"%s\" has no extension.\n", inputPath);
|
||||
|
||||
if (outputFileExtension == NULL)
|
||||
FATAL_ERROR("Output file \"%s\" has no extension.\n", outputPath);
|
||||
|
||||
int numGlyphs;
|
||||
int bpp;
|
||||
int layout;
|
||||
|
||||
if (!ParseNumber(argv[3], NULL, 10, &numGlyphs))
|
||||
FATAL_ERROR("Failed to parse number of glyphs.\n");
|
||||
|
||||
if (!ParseNumber(argv[4], NULL, 10, &layout))
|
||||
FATAL_ERROR("Failed to parse layout type.\n");
|
||||
|
||||
if (layout < 0 || layout > 2)
|
||||
FATAL_ERROR("Layout type %d is invalid. Layout type must be 0, 1, or 2.\n", layout);
|
||||
|
||||
bool toPng;
|
||||
|
||||
if (!strcmp(inputFileExtension, "png") && (bpp = ExtensionToBpp(outputFileExtension)) != 0)
|
||||
toPng = false;
|
||||
else if ((bpp = ExtensionToBpp(inputFileExtension)) != 0 && !strcmp(outputFileExtension, "png"))
|
||||
toPng = true;
|
||||
else
|
||||
FATAL_ERROR("Don't know how to convert \"%s\" to \"%s\".\n", inputPath, outputPath);
|
||||
|
||||
if (bpp == 1 && layout == 2)
|
||||
FATAL_ERROR("Layout type 2 is not supported with 1 BPP fonts.\n");
|
||||
|
||||
struct Image image;
|
||||
|
||||
if (toPng)
|
||||
{
|
||||
ReadFont(inputPath, &image, numGlyphs, bpp, layout);
|
||||
WritePng(outputPath, &image);
|
||||
}
|
||||
else
|
||||
{
|
||||
image.bitDepth = 8;
|
||||
ReadPng(inputPath, &image);
|
||||
WriteFont(outputPath, &image, numGlyphs, bpp, layout);
|
||||
}
|
||||
}
|
124
tools/rsfont/util.c
Normal file
124
tools/rsfont/util.c
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include "global.h"
|
||||
#include "util.h"
|
||||
|
||||
bool ParseNumber(char *s, char **end, int radix, int *intValue)
|
||||
{
|
||||
char *localEnd;
|
||||
|
||||
if (end == NULL)
|
||||
end = &localEnd;
|
||||
|
||||
errno = 0;
|
||||
|
||||
const long longValue = strtol(s, end, radix);
|
||||
|
||||
if (*end == s)
|
||||
return false; // not a number
|
||||
|
||||
if ((longValue == LONG_MIN || longValue == LONG_MAX) && errno == ERANGE)
|
||||
return false;
|
||||
|
||||
if (longValue > INT_MAX)
|
||||
return false;
|
||||
|
||||
if (longValue < INT_MIN)
|
||||
return false;
|
||||
|
||||
*intValue = (int)longValue;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char *GetFileExtension(char *path)
|
||||
{
|
||||
char *extension = path;
|
||||
|
||||
while (*extension != 0)
|
||||
extension++;
|
||||
|
||||
while (extension > path && *extension != '.')
|
||||
extension--;
|
||||
|
||||
if (extension == path)
|
||||
return NULL;
|
||||
|
||||
extension++;
|
||||
|
||||
if (*extension == 0)
|
||||
return NULL;
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
unsigned char *ReadWholeFile(char *path, int *size)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
|
||||
*size = ftell(fp);
|
||||
|
||||
unsigned char *buffer = malloc(*size);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
|
||||
|
||||
rewind(fp);
|
||||
|
||||
if (fread(buffer, *size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for reading.\n", path);
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
|
||||
*size = ftell(fp);
|
||||
|
||||
unsigned char *buffer = calloc(*size + padAmount, 1);
|
||||
|
||||
if (buffer == NULL)
|
||||
FATAL_ERROR("Failed to allocate memory for reading \"%s\".\n", path);
|
||||
|
||||
rewind(fp);
|
||||
|
||||
if (fread(buffer, *size, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to read \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void WriteWholeFile(char *path, void *buffer, int bufferSize)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
|
||||
if (fp == NULL)
|
||||
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
|
||||
|
||||
if (fwrite(buffer, bufferSize, 1, fp) != 1)
|
||||
FATAL_ERROR("Failed to write to \"%s\".\n", path);
|
||||
|
||||
fclose(fp);
|
||||
}
|
14
tools/rsfont/util.h
Normal file
14
tools/rsfont/util.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015 YamaArashi
|
||||
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool ParseNumber(char *s, char **end, int radix, int *intValue);
|
||||
char *GetFileExtension(char *path);
|
||||
unsigned char *ReadWholeFile(char *path, int *size);
|
||||
unsigned char *ReadWholeFileZeroPadded(char *path, int *size, int padAmount);
|
||||
void WriteWholeFile(char *path, void *buffer, int bufferSize);
|
||||
|
||||
#endif // UTIL_H
|
1
tools/scaninc/.gitignore
vendored
Normal file
1
tools/scaninc/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
scaninc
|
19
tools/scaninc/LICENSE
Normal file
19
tools/scaninc/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2015 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.
|
15
tools/scaninc/Makefile
Normal file
15
tools/scaninc/Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
CXX = g++
|
||||
|
||||
CXXFLAGS = -Wall -Werror -std=c++11 -O2 -s
|
||||
|
||||
SRCS = scaninc.cpp c_file.cpp asm_file.cpp
|
||||
|
||||
HEADERS := scaninc.h asm_file.h c_file.h
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
scaninc: $(SRCS) $(HEADERS)
|
||||
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(RM) scaninc scaninc.exe
|
191
tools/scaninc/asm_file.cpp
Normal file
191
tools/scaninc/asm_file.cpp
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
119
tools/scaninc/asm_file.h
Normal file
119
tools/scaninc/asm_file.h
Normal file
@ -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
|
298
tools/scaninc/c_file.cpp
Normal file
298
tools/scaninc/c_file.cpp
Normal file
@ -0,0 +1,298 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
CFile::~CFile()
|
||||
{
|
||||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
SkipWhitespace();
|
||||
CheckInclude();
|
||||
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] == '\n')
|
||||
{
|
||||
m_pos++;
|
||||
m_lineNum++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_buffer[m_pos] == '\r' && m_buffer[m_pos + 1] == '\n')
|
||||
{
|
||||
m_pos += 2;
|
||||
m_lineNum++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CFile::ConsumeComment()
|
||||
{
|
||||
if (m_buffer[m_pos] == '/' && m_buffer[m_pos + 1] == '*')
|
||||
{
|
||||
m_pos += 2;
|
||||
while (m_buffer[m_pos] != '*' && m_buffer[m_pos + 1] != '/')
|
||||
{
|
||||
if (m_buffer[m_pos] == 0)
|
||||
return false;
|
||||
if (!ConsumeNewline())
|
||||
m_pos++;
|
||||
}
|
||||
m_pos += 2;
|
||||
return true;
|
||||
}
|
||||
else if (m_buffer[m_pos] == '/' && m_buffer[m_pos + 1] == '/')
|
||||
{
|
||||
m_pos += 2;
|
||||
while (!ConsumeNewline())
|
||||
{
|
||||
if (m_buffer[m_pos] == 0)
|
||||
return false;
|
||||
m_pos++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CFile::SkipWhitespace()
|
||||
{
|
||||
while (ConsumeHorizontalWhitespace() || ConsumeNewline() || ConsumeComment())
|
||||
;
|
||||
}
|
||||
|
||||
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::CheckInclude()
|
||||
{
|
||||
if (m_buffer[m_pos] != '#')
|
||||
return;
|
||||
|
||||
std::string ident = "#include";
|
||||
|
||||
if (!CheckIdentifier(ident))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_pos += ident.length();
|
||||
|
||||
ConsumeHorizontalWhitespace();
|
||||
|
||||
std::string path = ReadPath();
|
||||
|
||||
if (!path.empty()) {
|
||||
m_includes.emplace(path);
|
||||
}
|
||||
}
|
||||
|
||||
void CFile::CheckIncbin()
|
||||
{
|
||||
// Optimization: assume most lines are not incbins
|
||||
if (!(m_buffer[m_pos+0] == 'I'
|
||||
&& m_buffer[m_pos+1] == 'N'
|
||||
&& m_buffer[m_pos+2] == 'C'
|
||||
&& m_buffer[m_pos+3] == 'B'
|
||||
&& m_buffer[m_pos+4] == 'I'
|
||||
&& m_buffer[m_pos+5] == 'N'
|
||||
&& m_buffer[m_pos+6] == '_'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
std::string path = ReadPath();
|
||||
|
||||
SkipWhitespace();
|
||||
|
||||
if (m_buffer[m_pos] != ')')
|
||||
FATAL_INPUT_ERROR("expected ')'");
|
||||
|
||||
m_pos++;
|
||||
|
||||
m_incbins.emplace(path);
|
||||
}
|
||||
|
||||
std::string CFile::ReadPath()
|
||||
{
|
||||
if (m_buffer[m_pos] != '"')
|
||||
{
|
||||
if (m_buffer[m_pos] == '<')
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
FATAL_INPUT_ERROR("expected '\"' or '<'");
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
|
||||
m_pos++;
|
||||
|
||||
return std::string(m_buffer + startPos, m_pos - 1 - startPos);
|
||||
}
|
57
tools/scaninc/c_file.h
Normal file
57
tools/scaninc/c_file.h
Normal file
@ -0,0 +1,57 @@
|
||||
// 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; }
|
||||
const std::set<std::string>& GetIncludes() { return m_includes; }
|
||||
|
||||
private:
|
||||
char *m_buffer;
|
||||
int m_pos;
|
||||
int m_size;
|
||||
int m_lineNum;
|
||||
std::string m_path;
|
||||
std::set<std::string> m_incbins;
|
||||
std::set<std::string> m_includes;
|
||||
|
||||
bool ConsumeHorizontalWhitespace();
|
||||
bool ConsumeNewline();
|
||||
bool ConsumeComment();
|
||||
void SkipWhitespace();
|
||||
bool CheckIdentifier(const std::string& ident);
|
||||
void CheckInclude();
|
||||
void CheckIncbin();
|
||||
std::string ReadPath();
|
||||
};
|
||||
|
||||
#endif // C_FILE_H
|
165
tools/scaninc/scaninc.cpp
Normal file
165
tools/scaninc/scaninc.cpp
Normal file
@ -0,0 +1,165 @@
|
||||
// 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 <list>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "scaninc.h"
|
||||
#include "asm_file.h"
|
||||
#include "c_file.h"
|
||||
|
||||
bool CanOpenFile(std::string path)
|
||||
{
|
||||
FILE *fp = std::fopen(path.c_str(), "rb");
|
||||
|
||||
if (fp == NULL)
|
||||
return false;
|
||||
|
||||
std::fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *const USAGE = "Usage: scaninc [-I INCLUDE_PATH] FILE_PATH\n";
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
std::queue<std::string> filesToProcess;
|
||||
std::set<std::string> dependencies;
|
||||
|
||||
std::list<std::string> includeDirs;
|
||||
|
||||
argc--;
|
||||
argv++;
|
||||
|
||||
while (argc > 1)
|
||||
{
|
||||
std::string arg(argv[0]);
|
||||
if (arg.substr(0, 2) == "-I")
|
||||
{
|
||||
std::string includeDir = arg.substr(2);
|
||||
if (includeDir.empty())
|
||||
{
|
||||
argc--;
|
||||
argv++;
|
||||
includeDir = std::string(argv[0]);
|
||||
}
|
||||
if (includeDir.back() != '/')
|
||||
{
|
||||
includeDir += '/';
|
||||
}
|
||||
includeDirs.push_back(includeDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
FATAL_ERROR(USAGE);
|
||||
}
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
|
||||
if (argc != 1) {
|
||||
FATAL_ERROR(USAGE);
|
||||
}
|
||||
|
||||
std::string initialPath(argv[0]);
|
||||
|
||||
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);
|
||||
|
||||
std::string srcDir("");
|
||||
std::size_t slash = initialPath.rfind('/');
|
||||
if (slash != std::string::npos)
|
||||
{
|
||||
srcDir = initialPath.substr(0, slash + 1);
|
||||
}
|
||||
includeDirs.push_back(srcDir);
|
||||
|
||||
if (extension == "c" || extension == "h")
|
||||
{
|
||||
filesToProcess.push(initialPath);
|
||||
|
||||
while (!filesToProcess.empty())
|
||||
{
|
||||
CFile file(filesToProcess.front());
|
||||
filesToProcess.pop();
|
||||
|
||||
file.FindIncbins();
|
||||
for (auto incbin : file.GetIncbins())
|
||||
{
|
||||
dependencies.insert(incbin);
|
||||
}
|
||||
for (auto include : file.GetIncludes())
|
||||
{
|
||||
for (auto includeDir : includeDirs)
|
||||
{
|
||||
std::string path(includeDir + include);
|
||||
if (CanOpenFile(path))
|
||||
{
|
||||
bool inserted = dependencies.insert(path).second;
|
||||
if (inserted)
|
||||
{
|
||||
filesToProcess.push(path);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (extension == "s" || extension == "inc")
|
||||
{
|
||||
filesToProcess.push(initialPath);
|
||||
|
||||
while (!filesToProcess.empty())
|
||||
{
|
||||
AsmFile file(filesToProcess.front());
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
59
tools/scaninc/scaninc.h
Normal file
59
tools/scaninc/scaninc.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user