Merge branch 'master' of github.com:pret/pokefirered

This commit is contained in:
PikalaxALT
2018-12-21 11:44:02 -05:00
94 changed files with 12602 additions and 1 deletions
+1 -1
View File
@@ -17,6 +17,7 @@
sound/**/*.bin
src/*.s
tools/agbcc
tools/binutils
ld_script_ruby.txt
ld_script_sapphire.txt
*.map
@@ -39,4 +40,3 @@ build/
.idea/
*.DS_Store
*.pl
tools/
+2
View File
@@ -0,0 +1,2 @@
aif2pcm
+20
View 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
View File
@@ -0,0 +1,15 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wno-switch -Werror -std=c11 -O2
LIBS = -lm
SRCS = main.c extended.c
.PHONY: clean
aif2pcm: $(SRCS)
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS) $(LIBS)
clean:
$(RM) aif2pcm aif2pcm.exe
+172
View 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
View 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,0,0,0,0,0,0};
read_aif(aif, &aif_data);
int header_size = 0x10;
struct Bytes *pcm;
struct Bytes output = {0,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
View File
@@ -0,0 +1 @@
bin2c
+19
View 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
View File
@@ -0,0 +1,13 @@
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c11 -O2
.PHONY: clean
SRCS = bin2c.c
bin2c: $(SRCS)
$(CC) $(CFLAGS) $(SRCS) -o $@ $(LDFLAGS)
clean:
$(RM) bin2c bin2c.exe
+201
View 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;
}
+2
View File
@@ -0,0 +1,2 @@
gbafix
README
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+10
View File
@@ -0,0 +1,10 @@
CC = gcc
.PHONY: clean
SRCS = gbafix.c
gbafix: $(SRCS)
$(CC) $(SRCS) -o $@ $(LDFLAGS)
clean:
$(RM) gbafix gbafix.exe
+289
View File
@@ -0,0 +1,289 @@
/*
"$Id: gbafix.c,v 1.2 2008-07-30 17:12:51 wntrmute Exp $"
DevkitPro GBA ROM fix utility
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
Please report all bugs and problems through the bug tracker at
"http://sourceforge.net/tracker/?group_id=114505&atid=668551".
"$Header: /lvm/shared/ds/ds/cvs/devkitpro-cvsbackup/tools/gba/gbatools/gbafix.c,v 1.2 2008-07-30 17:12:51 wntrmute Exp $"
*/
//---------------------------------------------------------------------------------
// gbafix.c
//---------------------------------------------------------------------------------
/*
Gameboy Advance ROM fixer (by Dark Fader / BlackThunder / WinterMute / Diegoisawesome)
Validates header of GBA roms.
History
-------
v1.06 - added output silencing, (Diegoisawesome)
v1.05 - added debug offset argument, (Diegoisawesome)
v1.04 - converted to plain C, (WinterMute)
v1.03 - header.fixed, header.device_type
v1.02 - redefined the options (rgbfix style), checksum=0
v1.01 - fix in parameters
v1.00 - logo, complement
*/
#pragma pack(1)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define VER "1.06"
#define ARGV argv[arg]
#define VALUE (ARGV+2)
#define NUMBER strtoul(VALUE, NULL, 0)
typedef struct
{
uint32_t start_code; // B instruction
uint8_t logo[0xA0-0x04]; // logo data
uint8_t title[0xC]; // game title name
uint32_t game_code; //
uint16_t maker_code; //
uint8_t fixed; // 0x96
uint8_t unit_code; // 0x00
uint8_t device_type; // 0x00
uint8_t unused[7]; //
uint8_t game_version; // 0x00
uint8_t complement; // 800000A0..800000BC
uint16_t checksum; // 0x0000
} Header;
Header header;
unsigned short checksum_without_header = 0;
const Header good_header =
{
// start_code
0xEA00002E,
// logo
{ 0x24,0xFF,0xAE,0x51,0x69,0x9A,0xA2,0x21,0x3D,0x84,0x82,0x0A,0x84,0xE4,0x09,0xAD,
0x11,0x24,0x8B,0x98,0xC0,0x81,0x7F,0x21,0xA3,0x52,0xBE,0x19,0x93,0x09,0xCE,0x20,
0x10,0x46,0x4A,0x4A,0xF8,0x27,0x31,0xEC,0x58,0xC7,0xE8,0x33,0x82,0xE3,0xCE,0xBF,
0x85,0xF4,0xDF,0x94,0xCE,0x4B,0x09,0xC1,0x94,0x56,0x8A,0xC0,0x13,0x72,0xA7,0xFC,
0x9F,0x84,0x4D,0x73,0xA3,0xCA,0x9A,0x61,0x58,0x97,0xA3,0x27,0xFC,0x03,0x98,0x76,
0x23,0x1D,0xC7,0x61,0x03,0x04,0xAE,0x56,0xBF,0x38,0x84,0x00,0x40,0xA7,0x0E,0xFD,
0xFF,0x52,0xFE,0x03,0x6F,0x95,0x30,0xF1,0x97,0xFB,0xC0,0x85,0x60,0xD6,0x80,0x25,
0xA9,0x63,0xBE,0x03,0x01,0x4E,0x38,0xE2,0xF9,0xA2,0x34,0xFF,0xBB,0x3E,0x03,0x44,
0x78,0x00,0x90,0xCB,0x88,0x11,0x3A,0x94,0x65,0xC0,0x7C,0x63,0x87,0xF0,0x3C,0xAF,
0xD6,0x25,0xE4,0x8B,0x38,0x0A,0xAC,0x72,0x21,0xD4,0xF8,0x07 } ,
// title
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
// game code
0x00000000,
// maker code
0x3130,
// fixed
0x96,
// unit_code
0x00,
// device type
0x00,
// unused
{ 0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
// game version
0x00,
// complement
0x00,
// checksum
0x0000
};
//---------------------------------------------------------------------------------
char HeaderComplement()
/*---------------------------------------------------------------------------------
Calculate Header complement check
---------------------------------------------------------------------------------*/
{
int n;
char c = 0;
char *p = (char *)&header + 0xA0;
for (n=0; n<0xBD-0xA0; n++)
{
c += *p++;
}
return -(0x19+c);
}
//---------------------------------------------------------------------------------
int main(int argc, char *argv[])
//---------------------------------------------------------------------------------
{
int arg;
char *argfile = 0;
FILE *infile;
int silent = 0;
int size,bit;
// show syntax
if (argc <= 1)
{
printf("GBA ROM fixer v"VER" by Dark Fader / BlackThunder / WinterMute / Diegoisawesome \n");
printf("Syntax: gbafix <rom.gba> [-p] [-t[title]] [-c<game_code>] [-m<maker_code>] [-r<version>] [-d<debug>] [--silent]\n");
printf("\n");
printf("parameters:\n");
printf(" -p Pad to next exact power of 2. No minimum size!\n");
printf(" -t[<title>] Patch title. Stripped filename if none given.\n");
printf(" -c<game_code> Patch game code (four characters)\n");
printf(" -m<maker_code> Patch maker code (two characters)\n");
printf(" -r<version> Patch game version (number)\n");
printf(" -d<debug> Enable debugging handler and set debug entry point (0 or 1)\n");
printf(" --silent Silence non-error output\n");
return -1;
}
// get filename
for (arg=1; arg<argc; arg++)
{
if ((ARGV[0] != '-')) { argfile=ARGV; }
if (strncmp("--silent", &ARGV[0], 7) == 0) { silent = 1; }
}
// check filename
if (!argfile)
{
fprintf(stderr, "Filename needed!\n");
return -1;
}
// read file
infile = fopen(argfile, "r+b");
if (!infile) { fprintf(stderr, "Error opening input file!\n"); return -1; }
fseek(infile, 0, SEEK_SET);
fread(&header, sizeof(header), 1, infile);
// fix some data
memcpy(header.logo, good_header.logo, sizeof(header.logo));
memcpy(&header.fixed, &good_header.fixed, sizeof(header.fixed));
memcpy(&header.device_type, &good_header.device_type, sizeof(header.device_type));
// parse command line
for (arg=1; arg<argc; arg++)
{
if ((ARGV[0] == '-'))
{
switch (ARGV[1])
{
case 'p': // pad
{
fseek(infile, 0, SEEK_END);
size = ftell(infile);
for (bit=31; bit>=0; bit--) if (size & (1<<bit)) break;
if (size != (1<<bit))
{
int todo = (1<<(bit+1)) - size;
while (todo--) fputc(0xFF, infile);
}
fseek(infile, 0, SEEK_SET);
break;
}
case 't': // title
{
char title[256];
memset(title, 0, sizeof(title));
if (VALUE[0])
{
strncpy(title, VALUE, sizeof(header.title));
}
else
{
// use filename
char s[256], *begin=s, *t; strcpy(s, argfile);
t = strrchr(s, '\\'); if (t) begin = t+1;
t = strrchr(s, '/'); if (t) begin = t+1;
t = strrchr(s, '.'); if (t) *t = 0;
strncpy(title, begin, sizeof(header.title));
if (!silent) printf("%s\n",begin);
}
memcpy(header.title, title, sizeof(header.title)); // copy
break;
}
case 'c': // game code
{
//if (!VALUE[0]) { fprintf(stderr, "Need value for %s\n", ARGV); break; }
//header.game_code = NUMBER;
header.game_code = VALUE[0] | VALUE[1]<<8 | VALUE[2]<<16 | VALUE[3]<<24;
break;
}
case 'm': // maker code
{
//if (!VALUE[0]) { fprintf(stderr, "Need value for %s\n", ARGV); break; }
//header.maker_code = (unsigned short)NUMBER;
header.maker_code = VALUE[0] | VALUE[1]<<8;
break;
}
case 'v': // ignored, compatability with other gbafix
{
break;
}
case 'r': // version
{
if (!VALUE[0]) { fprintf(stderr, "Need value for %s\n", ARGV); break; }
header.game_version = (unsigned char)NUMBER;
break;
}
case 'd': // debug
{
if (!VALUE[0]) { fprintf(stderr, "Need value for %s\n", ARGV); break; }
header.logo[0x9C-0x04] = 0xA5; // debug enable
header.device_type = (unsigned char)((NUMBER & 1) << 7); // debug handler entry point
break;
}
case '-': // long arguments
{
if (strncmp("silent", &ARGV[2], 6) == 0) { continue; }
break;
}
default:
{
printf("Invalid option: %s\n", ARGV);
}
}
}
}
// update complement check & total checksum
header.complement = 0;
header.checksum = 0; // must be 0
header.complement = HeaderComplement();
//header.checksum = checksum_without_header + HeaderChecksum();
fseek(infile, 0, SEEK_SET);
fwrite(&header, sizeof(header), 1, infile);
fclose(infile);
if (!silent) printf("ROM fixed!\n");
return 0;
}
+1
View File
@@ -0,0 +1 @@
gbagfx
+19
View 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.
+18
View File
@@ -0,0 +1,18 @@
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -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: all clean
all: gbagfx
@:
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
+254
View File
@@ -0,0 +1,254 @@
// 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;
}
static unsigned char *ConvertBitDepth(unsigned char *src, int srcBitDepth, int destBitDepth, int numPixels)
{
// Round the number of bits up to the next 8 and divide by 8 to get the number of bytes.
int srcSize = ((numPixels * srcBitDepth + 7) & ~7) / 8;
int destSize = ((numPixels * destBitDepth + 7) & ~7) / 8;
unsigned char *output = calloc(destSize, 1);
unsigned char *dest = output;
int i;
int j;
int destBit = 8 - destBitDepth;
for (i = 0; i < srcSize; i++)
{
unsigned char srcByte = src[i];
for (j = 8 - srcBitDepth; j >= 0; j -= srcBitDepth)
{
unsigned char pixel = (srcByte >> j) % (1 << srcBitDepth);
if (pixel >= (1 << destBitDepth))
FATAL_ERROR("Image exceeds the maximum color value for a %ibpp image.\n", destBitDepth);
*dest |= pixel << destBit;
destBit -= destBitDepth;
if (destBit < 0)
{
dest++;
destBit = 8 - destBitDepth;
}
}
}
return output;
}
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);
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);
if (bit_depth != image->bitDepth)
{
unsigned char *src = image->pixels;
if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && bit_depth != 8)
FATAL_ERROR("Bit depth of image must be 1, 2, 4, or 8.\n");
image->pixels = ConvertBitDepth(image->pixels, bit_depth, image->bitDepth, image->width * image->height);
free(src);
image->bitDepth = bit_depth;
}
}
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
View 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
View 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
View 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
+535
View File
@@ -0,0 +1,535 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.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 inline void swap_bytes(unsigned char * orig, unsigned char * dest) {
unsigned char tmp = *orig;
*orig = *dest;
*dest = tmp;
}
static inline unsigned char swap_nybbles(unsigned char orig)
{
return (orig >> 4) | (orig << 4);
}
static inline void swap_bytes_nybswap(unsigned char * orig, unsigned char * dest) {
unsigned char tmp = swap_nybbles(*orig);
*orig = swap_nybbles(*dest);
*dest = tmp;
}
static inline unsigned char reverse_bits(unsigned char orig) {
unsigned char dest = 0;
for (int i = 0; i < 8; i++)
{
dest <<= 1;
dest |= orig & 1;
orig >>= 1;
}
return dest;
}
static void vflip(unsigned char * tile, int bitDepth) {
for (int x = 0; x < bitDepth; x++)
{
unsigned char * col = tile + x;
swap_bytes(col + 0 * bitDepth, col + 7 * bitDepth);
swap_bytes(col + 1 * bitDepth, col + 6 * bitDepth);
swap_bytes(col + 2 * bitDepth, col + 5 * bitDepth);
swap_bytes(col + 3 * bitDepth, col + 4 * bitDepth);
}
}
static void hflip(unsigned char * tile, int bitDepth) {
for (int y = 0; y < 8; y++)
{
unsigned char * row = tile + y * bitDepth;
switch (bitDepth)
{
case 1:
*row = reverse_bits(*row);
break;
case 4:
swap_bytes_nybswap(row + 0, row + 3);
swap_bytes_nybswap(row + 1, row + 2);
break;
case 8:
swap_bytes(row + 0, row + 56);
swap_bytes(row + 8, row + 48);
swap_bytes(row + 16, row + 40);
swap_bytes(row + 24, row + 32);
break;
}
}
}
static unsigned char * ApplyTilemap(struct Image *image, unsigned char * buffer, int bitDepth)
{
int tileSize = bitDepth * 8;
unsigned char * tiles = calloc(image->tileMap.numTiles, tileSize);
int i;
struct Tile tileInfo;
for (i = 0; i < image->tileMap.numTiles; i++) {
tileInfo = image->tileMap.data[i];
unsigned char * tile = tiles + i * tileSize;
memcpy(tile, buffer + tileInfo.index * tileSize, tileSize);
if (tileInfo.xflip)
hflip(tile, bitDepth);
if (tileInfo.yflip)
vflip(tile, bitDepth);
}
free(buffer);
return tiles;
}
static unsigned char * BuildTilemap(struct Image *image, unsigned char * buffer, int * bufferSize)
{
int tileSize = image->bitDepth * 8;
unsigned char * outputPixels = calloc(1024, tileSize);
int nTilesIn = image->height * image->width / 64;
image->tileMap.data = calloc(nTilesIn, sizeof(struct Tilemap));
image->tileMap.numTiles = nTilesIn;
int nTilesOut = 0;
unsigned char curTile1[tileSize];
unsigned char curTile2[tileSize];
for (int i = 0; i < nTilesIn; i++) {
bool xflip = false;
bool yflip = false;
int j;
memcpy(curTile1, buffer + i * tileSize, tileSize);
for (j = 0; j < nTilesOut; j++) {
memcpy(curTile2, outputPixels + j * tileSize, tileSize);
if (memcmp(curTile1, curTile2, tileSize) == 0)
break;
xflip = true;
hflip(curTile2, image->bitDepth);
if (memcmp(curTile1, curTile2, tileSize) == 0)
break;
yflip = true;
vflip(curTile2, image->bitDepth);
if (memcmp(curTile1, curTile2, tileSize) == 0)
break;
xflip = false;
hflip(curTile2, image->bitDepth);
if (memcmp(curTile1, curTile2, tileSize) == 0)
break;
yflip = false;
}
image->tileMap.data[i].index = j;
image->tileMap.data[i].xflip = xflip;
image->tileMap.data[i].yflip = yflip;
image->tileMap.data[i].palno = 0;
if (j >= nTilesOut) {
if (nTilesOut >= 1024)
FATAL_ERROR("Cannot reduce image to 1024 or fewer tiles.\n");
memcpy(outputPixels + nTilesOut * tileSize, curTile1, tileSize);
nTilesOut++;
}
}
free(buffer);
*bufferSize = nTilesOut * tileSize;
return outputPixels;
}
static void AdvanceMetatilePosition(int *subTileX, int *subTileY, int *metatileX, int *metatileY, int metatilesWide, int metatileWidth, int metatileHeight)
{
(*subTileX)++;
if (*subTileX == metatileWidth) {
*subTileX = 0;
(*subTileY)++;
if (*subTileY == metatileHeight) {
*subTileY = 0;
(*metatileX)++;
if (*metatileX == metatilesWide) {
*metatileX = 0;
(*metatileY)++;
}
}
}
}
static void ConvertFromTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = metatilesWide * metatileWidth;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
int destX = metatileX * metatileWidth + subTileX;
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;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertFromTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 4;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 4; k++) {
int destX = (metatileX * metatileWidth + subTileX) * 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;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 8;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int destY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 8; k++) {
int destX = (metatileX * metatileWidth + subTileX) * 8 + k;
unsigned char srcPixel = *src++;
if (invertColors)
srcPixel = 255 - srcPixel;
dest[destY * pitch + destX] = srcPixel;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertToTiles1Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = metatilesWide * metatileWidth;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
int srcX = metatileX * metatileWidth + subTileX;
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;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertToTiles4Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 4;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 4; k++) {
int srcX = (metatileX * metatileWidth + subTileX) * 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;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
static void ConvertToTiles8Bpp(unsigned char *src, unsigned char *dest, int numTiles, int metatilesWide, int metatileWidth, int metatileHeight, bool invertColors)
{
int subTileX = 0;
int subTileY = 0;
int metatileX = 0;
int metatileY = 0;
int pitch = (metatilesWide * metatileWidth) * 8;
for (int i = 0; i < numTiles; i++) {
for (int j = 0; j < 8; j++) {
int srcY = (metatileY * metatileHeight + subTileY) * 8 + j;
for (int k = 0; k < 8; k++) {
int srcX = (metatileX * metatileWidth + subTileX) * 8 + k;
unsigned char srcPixel = src[srcY * pitch + srcX];
if (invertColors)
srcPixel = 255 - srcPixel;
*dest++ = srcPixel;
}
}
AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, metatilesWide, metatileWidth, metatileHeight);
}
}
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors)
{
int tileSize = bitDepth * 8;
int fileSize;
unsigned char *buffer = ReadWholeFile(path, &fileSize);
int numTiles;
if (image->hasTilemap) {
buffer = ApplyTilemap(image, buffer, bitDepth);
numTiles = image->tileMap.numTiles;
}
else
numTiles = fileSize / tileSize;
int tilesHeight = (numTiles + tilesWidth - 1) / tilesWidth;
if (tilesWidth % metatileWidth != 0)
FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)\n", tilesWidth, metatileWidth);
if (tilesHeight % metatileHeight != 0)
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)\n", tilesHeight, metatileHeight);
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");
int metatilesWide = tilesWidth / metatileWidth;
switch (bitDepth) {
case 1:
ConvertFromTiles1Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 4:
ConvertFromTiles4Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 8:
ConvertFromTiles8Bpp(buffer, image->pixels, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
}
free(buffer);
}
void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, 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;
if (tilesWidth % metatileWidth != 0)
FATAL_ERROR("The width in tiles (%d) isn't a multiple of the specified metatile width (%d)", tilesWidth, metatileWidth);
if (tilesHeight % metatileHeight != 0)
FATAL_ERROR("The height in tiles (%d) isn't a multiple of the specified metatile height (%d)", tilesHeight, metatileHeight);
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");
int metatilesWide = tilesWidth / metatileWidth;
switch (bitDepth) {
case 1:
ConvertToTiles1Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 4:
ConvertToTiles4Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
case 8:
ConvertToTiles8Bpp(image->pixels, buffer, numTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
break;
}
if (image->hasTilemap)
buffer = BuildTilemap(image, buffer, &bufferSize);
WriteWholeFile(path, buffer, bufferSize);
free(buffer);
}
void FreeImage(struct Image *image)
{
free(image->pixels);
image->pixels = NULL;
if (image->hasTilemap && image->tileMap.data != NULL) {
free(image->tileMap.data);
image->tileMap.data = NULL;
image->tileMap.numTiles = 0;
}
}
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);
}
void ReadGbaTilemap(char *path, struct Tilemap *tileMap)
{
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);
tileMap->numTiles = fileSize / 2;
tileMap->data = malloc(tileMap->numTiles * sizeof(struct Tile));
for (int i = 0; i < tileMap->numTiles; i++)
{
uint16_t raw = data[2 * i + 0] | (data[2 * i + 1] << 8);
tileMap->data[i].index = raw & 0x3FF;
tileMap->data[i].xflip = raw & 0x400 ? 1 : 0;
tileMap->data[i].yflip = raw & 0x800 ? 1 : 0;
tileMap->data[i].palno = raw >> 12;
}
free(data);
}
void WriteGbaTilemap(char *path, struct Tilemap *tileMap)
{
FILE *fp = fopen(path, "wb");
if (fp == NULL)
FATAL_ERROR("Failed to open \"%s\" for writing.\n", path);
for (int i = 0; i < tileMap->numTiles; i++) {
uint16_t raw = tileMap->data[i].index
| (tileMap->data[i].xflip << 10)
| (tileMap->data[i].yflip << 11)
| (tileMap->data[i].palno << 12);
fputc(raw & 0xFF, fp);
fputc(raw >> 8, fp);
}
fclose(fp);
}
+53
View File
@@ -0,0 +1,53 @@
// 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 __attribute__((packed)) Tile {
unsigned short index:10;
unsigned short xflip:1;
unsigned short yflip:1;
unsigned short palno:4;
};
struct Tilemap {
struct Tile *data;
int numTiles;
};
struct Image {
int width;
int height;
int bitDepth;
unsigned char *pixels;
bool hasPalette;
struct Palette palette;
bool hasTransparency;
struct Tilemap tileMap;
bool hasTilemap;
};
void ReadImage(char *path, int tilesWidth, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
void WriteImage(char *path, int numTiles, int bitDepth, int metatileWidth, int metatileHeight, struct Image *image, bool invertColors);
void FreeImage(struct Image *image);
void ReadGbaPalette(char *path, struct Palette *palette);
void WriteGbaPalette(char *path, struct Palette *palette);
void ReadGbaTilemap(char *path, struct Tilemap *tileMap);
void WriteGbaTilemap(char *path, struct Tilemap *tileMap);
#endif // GFX_H
+31
View 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
View 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
View 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
+153
View File
@@ -0,0 +1,153 @@
// 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)
{
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
View 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, const int minDistance);
#endif // LZ_H
+539
View File
@@ -0,0 +1,539 @@
// Copyright (c) 2015 YamaArashi
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "global.h"
#include "util.h"
#include "options.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, struct GbaToPngOptions *options)
{
struct Image image;
if (options->paletteFilePath != NULL)
{
ReadGbaPalette(options->paletteFilePath, &image.palette);
image.hasPalette = true;
}
else
{
image.hasPalette = false;
}
if (options->tilemapFilePath != NULL)
{
ReadGbaTilemap(options->tilemapFilePath, &image.tileMap);
image.hasTilemap = true;
}
else
{
image.tileMap.data = NULL;
image.hasTilemap = false;
}
ReadImage(inputPath, options->width, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
image.hasTransparency = options->hasTransparency;
WritePng(outputPath, &image);
FreeImage(&image);
}
void ConvertPngToGba(char *inputPath, char *outputPath, struct PngToGbaOptions *options)
{
struct Image image;
image.bitDepth = options->bitDepth;
image.hasTilemap = options->tilemapFilePath == NULL ? false : true;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
ReadPng(inputPath, &image);
WriteImage(outputPath, options->numTiles, options->bitDepth, options->metatileWidth, options->metatileHeight, &image, !image.hasPalette);
if (image.hasTilemap)
WriteGbaTilemap(options->tilemapFilePath, &image.tileMap);
FreeImage(&image);
}
void HandleGbaToPngCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
char *inputFileExtension = GetFileExtension(inputPath);
struct GbaToPngOptions options;
options.paletteFilePath = NULL;
options.tilemapFilePath = NULL;
options.bitDepth = inputFileExtension[0] - '0';
options.hasTransparency = false;
options.width = 1;
options.metatileWidth = 1;
options.metatileHeight = 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++;
options.paletteFilePath = argv[i];
}
else if (strcmp(option, "-tilemap") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No tilemap file path following \"-tilemap\".\n");
i++;
options.tilemapFilePath = argv[i];
}
else if (strcmp(option, "-object") == 0)
{
options.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, &options.width))
FATAL_ERROR("Failed to parse width.\n");
if (options.width < 1)
FATAL_ERROR("Width must be positive.\n");
}
else if (strcmp(option, "-mwidth") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
FATAL_ERROR("Failed to parse metatile width.\n");
if (options.metatileWidth < 1)
FATAL_ERROR("metatile width must be positive.\n");
}
else if (strcmp(option, "-mheight") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile height value following \"-mheight\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
FATAL_ERROR("Failed to parse metatile height.\n");
if (options.metatileHeight < 1)
FATAL_ERROR("metatile height must be positive.\n");
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
}
}
if (options.metatileWidth > options.width)
options.width = options.metatileWidth;
ConvertGbaToPng(inputPath, outputPath, &options);
}
void HandlePngToGbaCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
char *outputFileExtension = GetFileExtension(outputPath);
int bitDepth = outputFileExtension[0] - '0';
struct PngToGbaOptions options;
options.numTiles = 0;
options.bitDepth = bitDepth;
options.metatileWidth = 1;
options.metatileHeight = 1;
options.tilemapFilePath = NULL;
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, &options.numTiles))
FATAL_ERROR("Failed to parse number of tiles.\n");
if (options.numTiles < 1)
FATAL_ERROR("Number of tiles must be positive.\n");
}
else if (strcmp(option, "-tilemap") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No tilemap path following \"-tilemap\".\n");
i++;
options.tilemapFilePath = argv[i];
}
else if (strcmp(option, "-mwidth") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile width value following \"-mwidth\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileWidth))
FATAL_ERROR("Failed to parse metatile width.\n");
if (options.metatileWidth < 1)
FATAL_ERROR("metatile width must be positive.\n");
}
else if (strcmp(option, "-mheight") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No metatile height value following \"-mheight\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &options.metatileHeight))
FATAL_ERROR("Failed to parse metatile height.\n");
if (options.metatileHeight < 1)
FATAL_ERROR("metatile height must be positive.\n");
}
else
{
FATAL_ERROR("Unrecognized option \"%s\".\n", option);
}
}
ConvertPngToGba(inputPath, outputPath, &options);
}
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;
image.hasTilemap = false;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
ReadLatinFont(inputPath, &image);
WritePng(outputPath, &image);
FreeImage(&image);
}
void HandlePngToLatinFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.hasTilemap = false;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
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;
image.hasTilemap = false;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
ReadHalfwidthJapaneseFont(inputPath, &image);
WritePng(outputPath, &image);
FreeImage(&image);
}
void HandlePngToHalfwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.hasTilemap = false;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
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;
image.hasTilemap = false;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
ReadFullwidthJapaneseFont(inputPath, &image);
WritePng(outputPath, &image);
FreeImage(&image);
}
void HandlePngToFullwidthJapaneseFontCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
struct Image image;
image.hasTilemap = false;
image.tileMap.data = NULL;
image.tileMap.numTiles = 0;
image.bitDepth = 2;
ReadPng(inputPath, &image);
WriteFullwidthJapaneseFont(outputPath, &image);
FreeImage(&image);
}
void HandleLZCompressCommand(char *inputPath, char *outputPath, int argc, char **argv)
{
int overflowSize = 0;
int minDistance = 2; // default, for compatibility with LZ77UnCompVram()
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 if (strcmp(option, "-search") == 0)
{
if (i + 1 >= argc)
FATAL_ERROR("No size following \"-overflow\".\n");
i++;
if (!ParseNumber(argv[i], NULL, 10, &minDistance))
FATAL_ERROR("Failed to parse LZ min search distance.\n");
if (minDistance < 1)
FATAL_ERROR("LZ min search distance 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, minDistance);
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);
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright (c) 2018 huderlem
#ifndef OPTIONS_H
#define OPTIONS_H
#include <stdbool.h>
struct GbaToPngOptions {
char *paletteFilePath;
char *tilemapFilePath;
int bitDepth;
bool hasTransparency;
int width;
int metatileWidth;
int metatileHeight;
};
struct PngToGbaOptions {
char *tilemapFilePath;
int numTiles;
int bitDepth;
int metatileWidth;
int metatileHeight;
};
#endif // OPTIONS_H
+149
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1 @@
mid2agb
+19
View 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.
+18
View File
@@ -0,0 +1,18 @@
CXX := g++
CXXFLAGS := -std=c++11 -O2 -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: all clean
all: mid2agb
@:
mid2agb: $(SRCS) $(HEADERS)
$(CXX) $(CXXFLAGS) $(SRCS) -o $@ $(LDFLAGS)
clean:
$(RM) mid2agb mid2agb.exe
+547
View File
@@ -0,0 +1,547 @@
// 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;
static int s_memaccOp;
static int s_memaccParam1;
static int s_memaccParam2;
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 PrintMemAcc(const Event& event)
{
switch (s_memaccOp)
{
case 0x00:
PrintByte("MEMACC, mem_set, 0x%02X, %u", s_memaccParam1, event.param2);
break;
case 0x01:
PrintByte("MEMACC, mem_add, 0x%02X, %u", s_memaccParam1, event.param2);
break;
case 0x02:
PrintByte("MEMACC, mem_sub, 0x%02X, %u", s_memaccParam1, event.param2);
break;
case 0x03:
PrintByte("MEMACC, mem_mem_set, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
break;
case 0x04:
PrintByte("MEMACC, mem_mem_add, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
break;
case 0x05:
PrintByte("MEMACC, mem_mem_sub, 0x%02X, 0x%02X", s_memaccParam1, event.param2);
break;
// TODO: everything else
case 0x06:
break;
case 0x07:
break;
case 0x08:
break;
case 0x09:
break;
case 0x0A:
break;
case 0x0B:
break;
case 0x0C:
break;
case 0x0D:
break;
case 0x0E:
break;
case 0x0F:
break;
case 0x10:
break;
case 0x11:
break;
case 0x46:
break;
case 0x47:
break;
case 0x48:
break;
case 0x49:
break;
case 0x4A:
break;
case 0x4B:
break;
case 0x4C:
break;
case 0x4D:
break;
case 0x4E:
break;
case 0x4F:
break;
case 0x50:
break;
case 0x51:
break;
default:
break;
}
PrintWait(event.time);
}
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:
PrintMemAcc(event);
break;
case 0x0D:
s_memaccOp = event.param2;
PrintWait(event.time);
break;
case 0x0E:
s_memaccParam1 = event.param2;
PrintWait(event.time);
break;
case 0x0F:
s_memaccParam2 = event.param2;
PrintWait(event.time);
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);
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());
PrintWait(g_initialWait);
PrintByte("KEYSH , %s_key%+d", g_asmLabel.c_str(), 0);
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
View 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
View 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
View 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
+233
View File
@@ -0,0 +1,233 @@
// 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 -L??? label for assembler (default:output_file)\n"
" -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 "";
}
static std::string BaseName(std::string s)
{
std::size_t posAfterSlash = s.find_last_of("/\\");
if (posAfterSlash == std::string::npos)
posAfterSlash = 0;
else
posAfterSlash++;
std::size_t dotPos = s.find_first_of('.', posAfterSlash);
if (dotPos > posAfterSlash && dotPos != std::string::npos)
s = s.substr(posAfterSlash, dotPos - posAfterSlash);
return s;
}
static const char *GetArgument(int argc, char **argv, int& index)
{
assert(index >= 0 && index < argc);
const char *option = argv[index];
assert(option != nullptr);
assert(option[0] == '-');
// If there is text following the letter, return that.
if (std::strlen(option) >= 3)
return option + 2;
// Otherwise, try to get the next arg.
if (index + 1 < argc)
{
index++;
return argv[index];
}
else
{
return nullptr;
}
}
int main(int argc, char** argv)
{
std::string inputFilename;
std::string outputFilename;
for (int i = 1; i < argc; i++)
{
const char *option = argv[i];
if (option[0] == '-' && option[1] != '\0')
{
const char *arg;
switch (std::toupper(option[1]))
{
case 'E':
g_exactGateTime = true;
break;
case 'G':
arg = GetArgument(argc, argv, i);
if (arg == nullptr)
PrintUsage();
g_voiceGroup = std::stoi(arg);
break;
case 'L':
arg = GetArgument(argc, argv, i);
if (arg == nullptr)
PrintUsage();
g_asmLabel = arg;
break;
case 'N':
g_compressionEnabled = false;
break;
case 'P':
arg = GetArgument(argc, argv, i);
if (arg == nullptr)
PrintUsage();
g_priority = std::stoi(arg);
break;
case 'R':
arg = GetArgument(argc, argv, i);
if (arg == nullptr)
PrintUsage();
g_reverb = std::stoi(arg);
break;
case 'V':
arg = GetArgument(argc, argv, i);
if (arg == nullptr)
PrintUsage();
g_masterVolume = std::stoi(arg);
break;
case 'X':
g_clocksPerBeat = 2;
break;
default:
PrintUsage();
}
}
else
{
if (inputFilename.empty())
inputFilename = argv[i];
else if (outputFilename.empty())
outputFilename = argv[i];
else
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 = BaseName(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
View 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
+964
View File
@@ -0,0 +1,964 @@
// 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;
static int s_runningStatus;
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;
s_runningStatus = 0;
}
void SkipEventData()
{
Skip(ReadVLQ());
}
void DetermineEventCategory(MidiEventCategory& category, int& typeChan, int& size)
{
typeChan = ReadInt8();
if (typeChan < 0x80)
{
// If data byte was found, use the running status.
ungetc(typeChan, g_inputFile);
typeChan = s_runningStatus;
}
if (typeChan == 0xFF)
{
category = MidiEventCategory::Meta;
size = 0;
s_runningStatus = 0;
}
else if (typeChan >= 0xF0)
{
category = MidiEventCategory::SysEx;
size = 0;
s_runningStatus = 0;
}
else if (typeChan >= 0x80)
{
category = MidiEventCategory::Control;
switch (typeChan >> 4)
{
case 0xC:
case 0xD:
size = 1;
break;
default:
size = 2;
break;
}
s_runningStatus = typeChan;
}
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)
{
// Save the current file position and running status
// which get modified by CheckNoteEnd.
long startPos = ftell(g_inputFile);
int savedRunningStatus = s_runningStatus;
event.param2 = 0;
while (!CheckNoteEnd(event))
;
Seek(startPos);
s_runningStatus = savedRunningStatus;
}
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 = 0x40u;
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)
{
if (events[index1].type != events[index2].type ||
events[index1].note != events[index2].note ||
events[index1].param1 != events[index2].param1 ||
events[index1].time != events[index2].time)
return false;
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
View 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
View 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
View 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
View File
@@ -0,0 +1 @@
preproc
+19
View 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
View File
@@ -0,0 +1,17 @@
CXX := g++
CXXFLAGS := -std=c++11 -O2 -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
View 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
View 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
+429
View File
@@ -0,0 +1,429 @@
// 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++;
std::printf("{");
while (true)
{
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++;
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);
}
SkipWhitespace();
if (m_buffer[m_pos] != ',')
break;
m_pos++;
}
if (m_buffer[m_pos] != ')')
RaiseError("expected ')'");
m_pos++;
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1 @@
ramscrgen
+19
View 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
View File
@@ -0,0 +1,15 @@
CXX := g++
CXXFLAGS := -std=c++11 -O2 -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
View 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
+272
View File
@@ -0,0 +1,272 @@
#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 std::string s_archiveFilePath;
static std::string s_archiveObjectPath;
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;
static std::uint32_t s_elfFileOffset;
struct Symbol
{
std::uint32_t nameOffset;
std::uint32_t size;
};
static void Seek(long offset)
{
if (std::fseek(s_file, s_elfFileOffset + 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 VerifyAr()
{
char expectedMagic[8] = {'!', '<', 'a', 'r', 'c', 'h', '>', '\n'};
char magic[8];
if (std::fread(magic, 8, 1, s_file) != 1)
FATAL_ERROR("error: failed to read AR magic from \"%s\"\n", s_archiveFilePath.c_str());
if (std::memcmp(magic, expectedMagic, 8) != 0)
FATAL_ERROR("error: AR magic did not match in \"%s\"\n", s_archiveFilePath.c_str());
}
static void ReadElfHeader()
{
Seek(0x20);
s_sectionHeaderOffset = ReadInt32();
Seek(0x2E);
s_sectionHeaderEntrySize = ReadInt16();
s_sectionCount = ReadInt16();
s_shstrtabIndex = ReadInt16();
}
static void FindArObj()
{
char file_ident[17] = {0};
char filesize_s[11] = {0};
char expectedEndMagic[2] = { 0x60, 0x0a };
char end_magic[2];
std::size_t filesize;
Seek(8);
while (!std::feof(s_file)) {
if (std::fread(file_ident, 16, 1, s_file) != 1)
FATAL_ERROR("error: failed to read file ident in \"%s\"\n", s_archiveFilePath.c_str());
Skip(32);
if (std::fread(filesize_s, 10, 1, s_file) != 1)
FATAL_ERROR("error: failed to read filesize in \"%s\"\n", s_archiveFilePath.c_str());
if (std::fread(end_magic, 2, 1, s_file) != 1)
FATAL_ERROR("error: failed to read end sentinel in \"%s\"\n", s_archiveFilePath.c_str());
if (std::memcmp(end_magic, expectedEndMagic, 2) != 0)
FATAL_ERROR("error: corrupted archive header in \"%s\" at \"%s\"\n", s_archiveFilePath.c_str(), file_ident);
char * ptr = std::strchr(file_ident, '/');
if (ptr != nullptr)
*ptr = 0;
filesize = std::strtoul(filesize_s, nullptr, 10);
if (std::strncmp(s_archiveObjectPath.c_str(), file_ident, 16) == 0) {
s_elfFileOffset = std::ftell(s_file);
return;
}
Skip(filesize);
}
FATAL_ERROR("error: could not find object \"%s\" in archive \"%s\"\n", s_archiveObjectPath.c_str(), s_archiveFilePath.c_str());
}
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());
}
static std::map<std::string, std::uint32_t> GetCommonSymbols_Shared()
{
VerifyElfIdent();
ReadElfHeader();
FindTableOffsets();
std::map<std::string, std::uint32_t> commonSymbols;
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;
}
std::map<std::string, std::uint32_t> GetCommonSymbolsFromLib(std::string sourcePath, std::string libpath)
{
std::size_t colonPos = libpath.find(':');
if (colonPos == std::string::npos)
FATAL_ERROR("error: missing colon separator in libfile \"%s\"\n", s_elfPath.c_str());
s_archiveObjectPath = libpath.substr(colonPos + 1);
s_archiveFilePath = sourcePath + "/" + libpath.substr(1, colonPos - 1);
s_elfPath = sourcePath + "/" + libpath.substr(1);
s_file = std::fopen(s_archiveFilePath.c_str(), "rb");
if (s_file == NULL)
FATAL_ERROR("error: failed to open \"%s\" for reading\n", s_archiveFilePath.c_str());
VerifyAr();
FindArObj();
return GetCommonSymbols_Shared();
}
std::map<std::string, std::uint32_t> GetCommonSymbols(std::string sourcePath, std::string path)
{
s_elfFileOffset = 0;
if (path[0] == '*')
return GetCommonSymbolsFromLib(sourcePath, path);
s_elfPath = sourcePath + "/" + path;
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());
return GetCommonSymbols_Shared();
}
+30
View 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 sourcePath, std::string path);
#endif // ELF_H
+187
View File
@@ -0,0 +1,187 @@
// 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;
if (filename[0] == '*') {
dotIndex = filename.find_last_of(':');
filename = filename.substr(dotIndex + 1);
}
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, std::string libSourcePath)
{
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, incFilename[0] == '*' ? libSourcePath : 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;
std::string libSourcePath;
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);
commaPos = commonSymPath.find(',');
if (commaPos == std::string::npos) {
libSourcePath = "tools/agbcc/lib";
} else {
libSourcePath = commonSymPath.substr(commaPos + 1);
commonSymPath = commonSymPath.substr(0, commaPos);
}
}
ConvertSymFile(symFileName, sectionName, lang, common, sourcePath, commonSymPath, libSourcePath);
return 0;
}
+49
View 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
View 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
View 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
View File
@@ -0,0 +1 @@
rsfont
+19
View 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
View File
@@ -0,0 +1,15 @@
CC = gcc
CFLAGS = -Wall -Wextra -Werror -std=c11 -O2 -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File
@@ -0,0 +1 @@
scaninc
+19
View 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
View File
@@ -0,0 +1,15 @@
CXX = g++
CXXFLAGS = -Wall -Werror -std=c++11 -O2
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
View 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
View 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
+307
View File
@@ -0,0 +1,307 @@
// 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++;
while (true)
{
SkipWhitespace();
std::string path = ReadPath();
SkipWhitespace();
m_incbins.emplace(path);
if (m_buffer[m_pos] != ',')
break;
m_pos++;
}
if (m_buffer[m_pos] != ')')
FATAL_INPUT_ERROR("expected ')'");
m_pos++;
}
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
View 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
View 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
View 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