Makefile Changes (#40)

* Remove existing recomp

* Compile ido-static-recomp instead of relying on precompiled version. Also introduce makefile changes to allow aarch64 devices to compile.

* Get mio0 from the source, and build it in the init.

* Workaround for sm64tools not ignoring it's build files.

* Fix cflags

* Fancy colours for build system

* Remove sm64tools submodule and just take the minimum required files instead.

* Remove ido-static-recomp submodule and just fetch latest from GH instead.

* Add support for using a venv in python.

* remove mio0-decompressor temp

* Fix the mio0-decompress files being somehow missing.

* Fix stray message about "fix_checksum"

* Update logo to be a VAR, and have a failed build say FAILED

* Add checksum fix back in.
This commit is contained in:
Ryan Myers 2023-12-04 16:36:31 -05:00 committed by GitHub
parent 3a38f87ea5
commit 065b566eb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 911 additions and 2553 deletions

3
.gitignore vendored
View File

@ -13,3 +13,6 @@ ctx.c.m2c
*.z64
*.bin
/build
tools/mio0
tools/ido-recomp
.venv/

115
Makefile
View File

@ -22,6 +22,12 @@ CC_CHECK_COMP ?= gcc
OBJDUMP_BUILD ?= 0
# Number of threads to compress with
N_THREADS ?= $(shell nproc)
# Whether to colorize build messages
COLOR ?= 1
# Whether to hide commands or not
VERBOSE ?= 0
# Command for printing messages during the make.
PRINT ?= printf
# Set prefix to mips binutils binaries (mips-linux-gnu-ld => 'mips-linux-gnu-') - Change at your own risk!
# In nearly all cases, not having 'mips-linux-gnu-*' binaries on the PATH is indicative of missing dependencies
@ -64,16 +70,46 @@ CPPFLAGS += -fno-dollars-in-identifiers -P
LDFLAGS := --no-check-sections --accept-unknown-input-arch --emit-relocs
UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)
ifeq ($(OS),Windows_NT)
$(error Native Windows is currently unsupported for building this repository, use WSL instead c:)
else ifeq ($(UNAME_S),Linux)
DETECTED_OS := linux
#Detect aarch64 devices (Like Raspberry Pi OS 64-bit)
#If it's found, then change the compiler to a version that can compile in 32 bit mode.
ifeq ($(UNAME_M),aarch64)
CC_CHECK_COMP := arm-linux-gnueabihf-gcc
endif
else ifeq ($(UNAME_S),Darwin)
DETECTED_OS := mac
MAKE := gmake
CPPFLAGS += -xc++
endif
# Support python venv's if one is installed.
PYTHON_VENV = .venv/bin/python3
ifneq "$(wildcard $(PYTHON_VENV) )" ""
PYTHON = $(PYTHON_VENV)
endif
ifeq ($(VERBOSE),0)
V := @
endif
ifeq ($(COLOR),1)
NO_COL := \033[0m
RED := \033[0;31m
GREEN := \033[0;32m
BLUE := \033[0;34m
YELLOW := \033[0;33m
BLINK := \033[33;5m
endif
# Common build print status function
define print
@$(PRINT) "$(GREEN)$(1) $(YELLOW)$(2)$(GREEN) -> $(BLUE)$(3)$(NO_COL)\n"
endef
#### Tools ####
ifneq ($(shell type $(MIPS_BINUTILS_PREFIX)ld >/dev/null 2>/dev/null; echo $$?), 0)
$(error Unable to find $(MIPS_BINUTILS_PREFIX)ld. Please install or build MIPS binutils, commonly mips-linux-gnu. (or set MIPS_BINUTILS_PREFIX if your MIPS binutils install uses another prefix))
@ -82,7 +118,7 @@ endif
### Compiler ###
IDO := $(TOOLS)/ido_recomp/$(DETECTED_OS)/5.3/cc
IDO := $(TOOLS)/ido-recomp/$(DETECTED_OS)/cc
AS := $(MIPS_BINUTILS_PREFIX)as
LD := $(MIPS_BINUTILS_PREFIX)ld
OBJCOPY := $(MIPS_BINUTILS_PREFIX)objcopy
@ -94,7 +130,7 @@ CAT := cat
ASM_PROC_FLAGS := --input-enc=utf-8 --output-enc=euc-jp --convert-statics=global-with-filename
SPLAT ?= $(TOOLS)/splat/split.py
SPLAT ?= $(PYTHON) $(TOOLS)/splat/split.py
SPLAT_YAML ?= $(TARGET).$(VERSION).yaml
COMPTOOL := $(TOOLS)/comptool.py
@ -117,10 +153,17 @@ CHECK_WARNINGS := -Wall -Wextra -Wimplicit-fallthrough -Wno-unknown-pragmas -Wno
MIPS_BUILTIN_DEFS := -DMIPSEB -D_MIPS_FPSET=16 -D_MIPS_ISA=2 -D_ABIO32=1 -D_MIPS_SIM=_ABIO32 -D_MIPS_SZINT=32 -D_MIPS_SZPTR=32
ifneq ($(RUN_CC_CHECK),0)
# The -MMD flags additionaly creates a .d file with the same name as the .o file.
CHECK_WARNINGS := -Wno-unused-variable
CHECK_WARNINGS := -Wno-unused-variable
CC_CHECK := $(CC_CHECK_COMP)
CC_CHECK_FLAGS := -MMD -MP -fno-builtin -fsyntax-only -funsigned-char -fdiagnostics-color -std=gnu89 -m32 -DNON_MATCHING -DAVOID_UB -DCC_CHECK=1
ifneq ($(WERROR), 0)
CC_CHECK_FLAGS := -MMD -MP -fno-builtin -fsyntax-only -funsigned-char -fdiagnostics-color -std=gnu89 -DNON_MATCHING -DAVOID_UB -DCC_CHECK=1
# Ensure that gcc treats the code as 32-bit
ifeq ($(UNAME_M),aarch64)
CC_CHECK_FLAGS += -march=armv7-a+fp
else
CC_CHECK_FLAGS += -m32
endif
ifneq ($(WERROR), 0)
CHECK_WARNINGS += -Werror
endif
else
@ -231,25 +274,30 @@ all: uncompressed
init:
@$(MAKE) clean
@$(MAKE) -s -C tools
@$(MAKE) decompress
@$(MAKE) extract -j $(N_THREADS)
@$(MAKE) all -j $(N_THREADS)
@$(MAKE) compressed
SF := ___ ___\n/ __|| _|\n\__ \| _|\n|___/|_|\n
uncompressed: $(ROM)
ifneq ($(COMPARE),0)
@echo "Calculating Rom Header Checksum..."
@echo "$(GREEN)Calculating Rom Header Checksum... $(YELLOW)$<$(NO_COL)"
@$(PYTHON) $(COMPTOOL) -r $(ROM) .
@md5sum $(ROM)
@md5sum -c $(TARGET).$(VERSION).uncompressed.md5
@md5sum --status -c $(TARGET).$(VERSION).uncompressed.md5 && \
$(PRINT) "$(BLUE)$(TARGET).$(VERSION).uncompressed.z64$(NO_COL): $(GREEN)OK$(NO_COL)\n$(YELLOW) $(SF)" || \
$(PRINT) "$(BLUE)$(TARGET).$(VERSION).uncompressed.z64 $(RED)FAILED$(NO_COL)\n"
endif
compressed: $(ROMC)
ifeq ($(COMPARE),1)
@echo "Calculating Rom Header Checksum..."
@echo "$(GREEN)Calculating Rom Header Checksum... $(YELLOW)$<$(NO_COL)"
@$(PYTHON) $(COMPTOOL) -r $(ROMC) .
@md5sum $(ROMC)
@md5sum -c $(TARGET).$(VERSION).md5
@md5sum --status -c $(TARGET).$(VERSION).md5 && \
$(PRINT) "$(BLUE)$(TARGET).$(VERSION).z64$(NO_COL): $(GREEN)OK$(NO_COL)\n" || \
$(PRINT) "$(BLUE)$(TARGET).$(VERSION).z64 $(RED)FAILED$(NO_COL)\n"
endif
#### Main Targets ###
@ -273,7 +321,7 @@ clean:
@git clean -fdx linker_scripts/*.ld
format:
@$(TOOLS)/format.py -j $(N_THREADS)
@$(PYTHON) $(TOOLS)/format.py -j $(N_THREADS)
checkformat:
@$(TOOLS)/check_format.sh -j $(N_THREADS)
@ -299,53 +347,58 @@ disasm:
# Final ROM
$(ROMC): $(BASEROM_UNCOMPRESSED)
@echo "Compressing ROM..."
$(call print,Compressing ROM...,$<,$@)
@$(PYTHON) $(COMPTOOL) -c $(ROM) $(ROMC)
# Uncompressed ROM
$(ROM): $(ELF)
@echo "ELF->ROM:"
$(OBJCOPY) -O binary $< $@
$(call print,ELF->ROM:,$<,$@)
$(V)$(OBJCOPY) -O binary $< $@
# Link
$(ELF): $(LIBULTRA_O) $(O_FILES) $(LD_SCRIPT) $(BUILD_DIR)/linker_scripts/$(VERSION)/hardware_regs.ld $(BUILD_DIR)/linker_scripts/$(VERSION)/undefined_syms.ld $(BUILD_DIR)/linker_scripts/$(VERSION)/pif_syms.ld $(BUILD_DIR)/linker_scripts/$(VERSION)/auto/undefined_syms_auto.ld $(BUILD_DIR)/linker_scripts/$(VERSION)/auto/undefined_funcs_auto.ld
@echo "Linking..."
$(LD) $(LDFLAGS) -T $(LD_SCRIPT) \
$(call print,Linking:,$<,$@)
$(V)$(LD) $(LDFLAGS) -T $(LD_SCRIPT) \
-T $(BUILD_DIR)/linker_scripts/$(VERSION)/hardware_regs.ld -T $(BUILD_DIR)/linker_scripts/$(VERSION)/undefined_syms.ld -T $(BUILD_DIR)/linker_scripts/$(VERSION)/pif_syms.ld \
-T $(BUILD_DIR)/linker_scripts/$(VERSION)/auto/undefined_syms_auto.ld -T $(BUILD_DIR)/linker_scripts/$(VERSION)/auto/undefined_funcs_auto.ld \
-Map $(LD_MAP) -o $@
# PreProcessor
$(BUILD_DIR)/%.ld: %.ld
$(CPP) $(CPPFLAGS) $(BUILD_DEFINES) $(IINC) $< > $@
$(call print,PreProcessor:,$<,$@)
$(V)$(CPP) $(CPPFLAGS) $(BUILD_DEFINES) $(IINC) $< > $@
# Binary
$(BUILD_DIR)/%.o: %.bin
$(OBJCOPY) -I binary -O elf32-big $< $@
$(call print,Binary:,$<,$@)
$(V)$(OBJCOPY) -I binary -O elf32-big $< $@
# Assembly
$(BUILD_DIR)/%.o: %.s
$(CPP) $(CPPFLAGS) $(BUILD_DEFINES) $(IINC) -I $(dir $*) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(AS_DEFINES) $< | $(ICONV) $(ICONV_FLAGS) | $(AS) $(ASFLAGS) $(ENDIAN) $(IINC) -I $(dir $*) -o $@
$(OBJDUMP_CMD)
$(call print,Assembling:,$<,$@)
$(V)$(CPP) $(CPPFLAGS) $(BUILD_DEFINES) $(IINC) -I $(dir $*) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(AS_DEFINES) $< | $(ICONV) $(ICONV_FLAGS) | $(AS) $(ASFLAGS) $(ENDIAN) $(IINC) -I $(dir $*) -o $@
$(V)$(OBJDUMP_CMD)
# C
$(BUILD_DIR)/%.o: %.c
$(CC_CHECK) $(CC_CHECK_FLAGS) $(IINC) -I $(dir $*) $(CHECK_WARNINGS) $(BUILD_DEFINES) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(MIPS_BUILTIN_DEFS) -o $@ $<
$(CC) -c $(CFLAGS) $(BUILD_DEFINES) $(IINC) $(WARNINGS) $(MIPS_VERSION) $(ENDIAN) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(OPTFLAGS) -o $@ $<
$(OBJDUMP_CMD)
$(RM_MDEBUG)
$(call print,Compiling:,$<,$@)
@$(CC_CHECK) $(CC_CHECK_FLAGS) $(IINC) -I $(dir $*) $(CHECK_WARNINGS) $(BUILD_DEFINES) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(MIPS_BUILTIN_DEFS) -o $@ $<
$(V)$(CC) -c $(CFLAGS) $(BUILD_DEFINES) $(IINC) $(WARNINGS) $(MIPS_VERSION) $(ENDIAN) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(OPTFLAGS) -o $@ $<
$(V)$(OBJDUMP_CMD)
$(V)$(RM_MDEBUG)
# Patch ll.o
build/src/libultra/libc/ll.o: src/libultra/libc/ll.c
$(CC_CHECK) $(CC_CHECK_FLAGS) $(IINC) -I $(dir $*) $(CHECK_WARNINGS) $(BUILD_DEFINES) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(MIPS_BUILTIN_DEFS) -o $@ $<
$(CC) -c $(CFLAGS) $(BUILD_DEFINES) $(IINC) $(WARNINGS) $(MIPS_VERSION) $(ENDIAN) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(OPTFLAGS) -o $@ $<
$(PYTHON) tools/set_o32abi_bit.py $@
$(OBJDUMP_CMD)
$(RM_MDEBUG)
$(call print,Patching:,$<,$@)
@$(CC_CHECK) $(CC_CHECK_FLAGS) $(IINC) -I $(dir $*) $(CHECK_WARNINGS) $(BUILD_DEFINES) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(MIPS_BUILTIN_DEFS) -o $@ $<
$(V)$(CC) -c $(CFLAGS) $(BUILD_DEFINES) $(IINC) $(WARNINGS) $(MIPS_VERSION) $(ENDIAN) $(COMMON_DEFINES) $(RELEASE_DEFINES) $(GBI_DEFINES) $(C_DEFINES) $(OPTFLAGS) -o $@ $<
$(V)$(PYTHON) tools/set_o32abi_bit.py $@
$(V)$(OBJDUMP_CMD)
$(V)$(RM_MDEBUG)
-include $(DEP_FILES)
# Print target for debugging
print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true
.PHONY: all uncompressed compressed clean init extract expected format checkformat decompress context disasm fix_checksum
.PHONY: all uncompressed compressed clean init extract expected format checkformat decompress context disasm

35
tools/Makefile Normal file
View File

@ -0,0 +1,35 @@
CC = gcc
UNAME_S := $(shell uname -s)
ifeq ($(OS),Windows_NT)
$(error Native Windows is currently unsupported for building this repository, use WSL instead c:)
else ifeq ($(UNAME_S),Linux)
DETECTED_OS := linux
else ifeq ($(UNAME_S),Darwin)
DETECTED_OS := mac
endif
RECOMP_DIR := ido-recomp/$(DETECTED_OS)
default: all
all: recomp mio0
clean:
$(RM) -rf $(RECOMP_DIR)
$(RM) mio0
distclean: clean
recomp:
@echo "Fetching Recomp..."
wget https://github.com/decompals/ido-static-recomp/releases/download/v1.0/ido-5.3-recomp-${DETECTED_OS}.tar.gz
mkdir -p $(RECOMP_DIR)
tar xf ido-5.3-recomp-${DETECTED_OS}.tar.gz -C $(RECOMP_DIR)
$(RM) ido-5.3-recomp-${DETECTED_OS}.tar.gz
mio0:
@echo "Building mio0..."
$(CC) -Wall -Wextra -Wno-format-overflow -O2 -ffunction-sections -fdata-sections -DMIO0_STANDALONE -s -Wl,--gc-sections -o mio0 mio0-decompressor/libmio0.c
.PHONY: all clean distclean default

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Q
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.

View File

@ -0,0 +1,6 @@
# mio-decompressor
Original Source can be found here: https://github.com/queueRAM/sm64tools
## License
MIT License. Copyright 2015 queueRAM.

View File

@ -0,0 +1,556 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "libmio0.h"
#include "utils.h"
// defines
#define MIO0_VERSION "0.1"
#define GET_BIT(buf, bit) ((buf)[(bit) / 8] & (1 << (7 - ((bit) % 8))))
// types
typedef struct
{
int *indexes;
int allocated;
int count;
int start;
} lookback;
// functions
#define LOOKBACK_COUNT 256
#define LOOKBACK_INIT_SIZE 128
static lookback *lookback_init(void)
{
lookback *lb = malloc(LOOKBACK_COUNT * sizeof(*lb));
for (int i = 0; i < LOOKBACK_COUNT; i++) {
lb[i].allocated = LOOKBACK_INIT_SIZE;
lb[i].indexes = malloc(lb[i].allocated * sizeof(*lb[i].indexes));
lb[i].count = 0;
lb[i].start = 0;
}
return lb;
}
static void lookback_free(lookback *lb)
{
for (int i = 0; i < LOOKBACK_COUNT; i++) {
free(lb[i].indexes);
}
free(lb);
}
static inline void lookback_push(lookback *lkbk, unsigned char val, int index)
{
lookback *lb = &lkbk[val];
if (lb->count == lb->allocated) {
lb->allocated *= 4;
lb->indexes = realloc(lb->indexes, lb->allocated * sizeof(*lb->indexes));
}
lb->indexes[lb->count++] = index;
}
static void PUT_BIT(unsigned char *buf, int bit, int val)
{
unsigned char mask = 1 << (7 - (bit % 8));
unsigned int offset = bit / 8;
buf[offset] = (buf[offset] & ~(mask)) | (val ? mask : 0);
}
// used to find longest matching stream in buffer
// buf: buffer
// start_offset: offset in buf to look back from
// max_search: max number of bytes to find
// found_offset: returned offset found (0 if none found)
// returns max length of matching stream (0 if none found)
static int find_longest(const unsigned char *buf, int start_offset, int max_search, int *found_offset, lookback *lkbk)
{
int best_length = 0;
int best_offset = 0;
int cur_length;
int search_len;
int farthest, off, i;
int lb_idx;
const unsigned char first = buf[start_offset];
lookback *lb = &lkbk[first];
// buf
// | off start max
// V |+i-> |+i-> |
// |--------------raw-data-----------------|
// |+i-> | |+i->
// +cur_length
// check at most the past 4096 values
farthest = MAX(start_offset - 4096, 0);
// find starting index
for (lb_idx = lb->start; lb_idx < lb->count && lb->indexes[lb_idx] < farthest; lb_idx++) {}
lb->start = lb_idx;
for ( ; lb_idx < lb->count && lb->indexes[lb_idx] < start_offset; lb_idx++) {
off = lb->indexes[lb_idx];
// check at most requested max or up until start
search_len = MIN(max_search, start_offset - off);
for (i = 0; i < search_len; i++) {
if (buf[start_offset + i] != buf[off + i]) {
break;
}
}
cur_length = i;
// if matched up until start, continue matching in already matched parts
if (cur_length == search_len) {
// check at most requested max less current length
search_len = max_search - cur_length;
for (i = 0; i < search_len; i++) {
if (buf[start_offset + cur_length + i] != buf[off + i]) {
break;
}
}
cur_length += i;
}
if (cur_length > best_length) {
best_offset = start_offset - off;
best_length = cur_length;
}
}
// return best reverse offset and length (may be 0)
*found_offset = best_offset;
return best_length;
}
// decode MIO0 header
// returns 1 if valid header, 0 otherwise
int mio0_decode_header(const unsigned char *buf, mio0_header_t *head)
{
if (!memcmp(buf, "MIO0", 4)) {
head->dest_size = read_u32_be(&buf[4]);
head->comp_offset = read_u32_be(&buf[8]);
head->uncomp_offset = read_u32_be(&buf[12]);
return 1;
}
return 0;
}
void mio0_encode_header(unsigned char *buf, const mio0_header_t *head)
{
memcpy(buf, "MIO0", 4);
write_u32_be(&buf[4], head->dest_size);
write_u32_be(&buf[8], head->comp_offset);
write_u32_be(&buf[12], head->uncomp_offset);
}
int mio0_decode(const unsigned char *in, unsigned char *out, unsigned int *end)
{
mio0_header_t head;
unsigned int bytes_written = 0;
int bit_idx = 0;
int comp_idx = 0;
int uncomp_idx = 0;
int valid;
// extract header
valid = mio0_decode_header(in, &head);
// verify MIO0 header
if (!valid) {
return -2;
}
// decode data
while (bytes_written < head.dest_size) {
if (GET_BIT(&in[MIO0_HEADER_LENGTH], bit_idx)) {
// 1 - pull uncompressed data
out[bytes_written] = in[head.uncomp_offset + uncomp_idx];
bytes_written++;
uncomp_idx++;
} else {
// 0 - read compressed data
int idx;
int length;
int i;
const unsigned char *vals = &in[head.comp_offset + comp_idx];
comp_idx += 2;
length = ((vals[0] & 0xF0) >> 4) + 3;
idx = ((vals[0] & 0x0F) << 8) + vals[1] + 1;
for (i = 0; i < length; i++) {
out[bytes_written] = out[bytes_written - idx];
bytes_written++;
}
}
bit_idx++;
}
if (end) {
*end = head.uncomp_offset + uncomp_idx;
}
return bytes_written;
}
int mio0_encode(const unsigned char *in, unsigned int length, unsigned char *out)
{
unsigned char *bit_buf;
unsigned char *comp_buf;
unsigned char *uncomp_buf;
unsigned int bit_length;
unsigned int comp_offset;
unsigned int uncomp_offset;
unsigned int bytes_proc = 0;
int bytes_written;
int bit_idx = 0;
int comp_idx = 0;
int uncomp_idx = 0;
lookback *lookbacks;
// initialize lookback buffer
lookbacks = lookback_init();
// allocate some temporary buffers worst case size
bit_buf = malloc((length + 7) / 8); // 1-bit/byte
comp_buf = malloc(length); // 16-bits/2bytes
uncomp_buf = malloc(length); // all uncompressed
memset(bit_buf, 0, (length + 7) / 8);
// encode data
// special case for first byte
lookback_push(lookbacks, in[0], 0);
uncomp_buf[uncomp_idx] = in[0];
uncomp_idx += 1;
bytes_proc += 1;
PUT_BIT(bit_buf, bit_idx++, 1);
while (bytes_proc < length) {
int offset;
int max_length = MIN(length - bytes_proc, 18);
int longest_match = find_longest(in, bytes_proc, max_length, &offset, lookbacks);
// push current byte before checking next longer match
lookback_push(lookbacks, in[bytes_proc], bytes_proc);
if (longest_match > 2) {
int lookahead_offset;
// lookahead to next byte to see if longer match
int lookahead_length = MIN(length - bytes_proc - 1, 18);
int lookahead_match = find_longest(in, bytes_proc + 1, lookahead_length, &lookahead_offset, lookbacks);
// better match found, use uncompressed + lookahead compressed
if ((longest_match + 1) < lookahead_match) {
// uncompressed byte
uncomp_buf[uncomp_idx] = in[bytes_proc];
uncomp_idx++;
PUT_BIT(bit_buf, bit_idx, 1);
bytes_proc++;
longest_match = lookahead_match;
offset = lookahead_offset;
bit_idx++;
lookback_push(lookbacks, in[bytes_proc], bytes_proc);
}
// first byte already pushed above
for (int i = 1; i < longest_match; i++) {
lookback_push(lookbacks, in[bytes_proc + i], bytes_proc + i);
}
// compressed block
comp_buf[comp_idx] = (((longest_match - 3) & 0x0F) << 4) |
(((offset - 1) >> 8) & 0x0F);
comp_buf[comp_idx + 1] = (offset - 1) & 0xFF;
comp_idx += 2;
PUT_BIT(bit_buf, bit_idx, 0);
bytes_proc += longest_match;
} else {
// uncompressed byte
uncomp_buf[uncomp_idx] = in[bytes_proc];
uncomp_idx++;
PUT_BIT(bit_buf, bit_idx, 1);
bytes_proc++;
}
bit_idx++;
}
// compute final sizes and offsets
// +7 so int division accounts for all bits
bit_length = ((bit_idx + 7) / 8);
// compressed data after control bits and aligned to 4-byte boundary
comp_offset = ALIGN(MIO0_HEADER_LENGTH + bit_length, 4);
uncomp_offset = comp_offset + comp_idx;
bytes_written = uncomp_offset + uncomp_idx;
// output header
memcpy(out, "MIO0", 4);
write_u32_be(&out[4], length);
write_u32_be(&out[8], comp_offset);
write_u32_be(&out[12], uncomp_offset);
// output data
memcpy(&out[MIO0_HEADER_LENGTH], bit_buf, bit_length);
memcpy(&out[comp_offset], comp_buf, comp_idx);
memcpy(&out[uncomp_offset], uncomp_buf, uncomp_idx);
// free allocated buffers
free(bit_buf);
free(comp_buf);
free(uncomp_buf);
lookback_free(lookbacks);
return bytes_written;
}
int mio0_decode_file(const char *in_file, unsigned long offset, const char *out_file)
{
mio0_header_t head;
FILE *in;
FILE *out;
unsigned char *in_buf = NULL;
unsigned char *out_buf = NULL;
long file_size;
int ret_val = 0;
size_t bytes_read;
int bytes_decoded;
int bytes_written;
int valid;
in = fopen(in_file, "rb");
if (in == NULL) {
return 1;
}
// allocate buffer to read from offset to end of file
fseek(in, 0, SEEK_END);
file_size = ftell(in);
in_buf = malloc(file_size - offset);
fseek(in, offset, SEEK_SET);
// read bytes
bytes_read = fread(in_buf, 1, file_size - offset, in);
if (bytes_read != file_size - offset) {
ret_val = 2;
goto free_all;
}
// verify header
valid = mio0_decode_header(in_buf, &head);
if (!valid) {
ret_val = 3;
goto free_all;
}
out_buf = malloc(head.dest_size);
// decompress MIO0 encoded data
bytes_decoded = mio0_decode(in_buf, out_buf, NULL);
if (bytes_decoded < 0) {
ret_val = 3;
goto free_all;
}
// open output file
out = fopen(out_file, "wb");
if (out == NULL) {
ret_val = 4;
goto free_all;
}
// write data to file
bytes_written = fwrite(out_buf, 1, bytes_decoded, out);
if (bytes_written != bytes_decoded) {
ret_val = 5;
}
// clean up
fclose(out);
free_all:
if (out_buf) {
free(out_buf);
}
if (in_buf) {
free(in_buf);
}
fclose(in);
return ret_val;
}
int mio0_encode_file(const char *in_file, const char *out_file)
{
FILE *in;
FILE *out;
unsigned char *in_buf = NULL;
unsigned char *out_buf = NULL;
size_t file_size;
size_t bytes_read;
int bytes_encoded;
int bytes_written;
int ret_val = 0;
in = fopen(in_file, "rb");
if (in == NULL) {
return 1;
}
// allocate buffer to read entire contents of files
fseek(in, 0, SEEK_END);
file_size = ftell(in);
fseek(in, 0, SEEK_SET);
in_buf = malloc(file_size);
// read bytes
bytes_read = fread(in_buf, 1, file_size, in);
if (bytes_read != file_size) {
ret_val = 2;
goto free_all;
}
// allocate worst case length
out_buf = malloc(MIO0_HEADER_LENGTH + ((file_size+7)/8) + file_size);
// compress data in MIO0 format
bytes_encoded = mio0_encode(in_buf, file_size, out_buf);
// open output file
out = fopen(out_file, "wb");
if (out == NULL) {
ret_val = 4;
goto free_all;
}
// write data to file
bytes_written = fwrite(out_buf, 1, bytes_encoded, out);
if (bytes_written != bytes_encoded) {
ret_val = 5;
}
// clean up
fclose(out);
free_all:
if (out_buf) {
free(out_buf);
}
if (in_buf) {
free(in_buf);
}
fclose(in);
return ret_val;
}
// mio0 standalone executable
#ifdef MIO0_STANDALONE
typedef struct
{
char *in_filename;
char *out_filename;
unsigned int offset;
int compress;
} arg_config;
static arg_config default_config =
{
NULL,
NULL,
0,
1
};
static void print_usage(void)
{
ERROR("Usage: mio0 [-c / -d] [-o OFFSET] FILE [OUTPUT]\n"
"\n"
"mio0 v" MIO0_VERSION ": MIO0 compression and decompression tool\n"
"\n"
"Optional arguments:\n"
" -c compress raw data into MIO0 (default: compress)\n"
" -d decompress MIO0 into raw data\n"
" -o OFFSET starting offset in FILE (default: 0)\n"
"\n"
"File arguments:\n"
" FILE input file\n"
" [OUTPUT] output file (default: FILE.out)\n");
exit(1);
}
// parse command line arguments
static void parse_arguments(int argc, char *argv[], arg_config *config)
{
int i;
int file_count = 0;
if (argc < 2) {
print_usage();
exit(1);
}
for (i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
switch (argv[i][1]) {
case 'c':
config->compress = 1;
break;
case 'd':
config->compress = 0;
break;
case 'o':
if (++i >= argc) {
print_usage();
}
config->offset = strtoul(argv[i], NULL, 0);
break;
default:
print_usage();
break;
}
} else {
switch (file_count) {
case 0:
config->in_filename = argv[i];
break;
case 1:
config->out_filename = argv[i];
break;
default: // too many
print_usage();
break;
}
file_count++;
}
}
if (file_count < 1) {
print_usage();
}
}
int main(int argc, char *argv[])
{
char out_filename[FILENAME_MAX];
arg_config config;
int ret_val;
// get configuration from arguments
config = default_config;
parse_arguments(argc, argv, &config);
if (config.out_filename == NULL) {
config.out_filename = out_filename;
sprintf(config.out_filename, "%s.out", config.in_filename);
}
// operation
if (config.compress) {
ret_val = mio0_encode_file(config.in_filename, config.out_filename);
} else {
ret_val = mio0_decode_file(config.in_filename, config.offset, config.out_filename);
}
switch (ret_val) {
case 1:
ERROR("Error opening input file \"%s\"\n", config.in_filename);
break;
case 2:
ERROR("Error reading from input file \"%s\"\n", config.in_filename);
break;
case 3:
ERROR("Error decoding MIO0 data. Wrong offset (0x%X)?\n", config.offset);
break;
case 4:
ERROR("Error opening output file \"%s\"\n", config.out_filename);
break;
case 5:
ERROR("Error writing bytes to output file \"%s\"\n", config.out_filename);
break;
}
return ret_val;
}
#endif // MIO0_STANDALONE

View File

@ -0,0 +1,50 @@
#ifndef LIBMIO0_H_
#define LIBMIO0_H_
// defines
#define MIO0_HEADER_LENGTH 16
// typedefs
typedef struct
{
unsigned int dest_size;
unsigned int comp_offset;
unsigned int uncomp_offset;
} mio0_header_t;
// function prototypes
// decode MIO0 header
// returns 1 if valid header, 0 otherwise
int mio0_decode_header(const unsigned char *buf, mio0_header_t *head);
// encode MIO0 header from struct
void mio0_encode_header(unsigned char *buf, const mio0_header_t *head);
// decode MIO0 data in memory
// in: buffer containing MIO0 data
// out: buffer for output data
// end: output offset of the last byte decoded from in (set to NULL if unwanted)
// returns bytes extracted to 'out' or negative value on failure
int mio0_decode(const unsigned char *in, unsigned char *out, unsigned int *end);
// encode MIO0 data in memory
// in: buffer containing raw data
// out: buffer for MIO0 data
// returns size of compressed data in 'out' including MIO0 header
int mio0_encode(const unsigned char *in, unsigned int length, unsigned char *out);
// decode an entire MIO0 block at an offset from file to output file
// in_file: input filename
// offset: offset to start decoding from in_file
// out_file: output filename
int mio0_decode_file(const char *in_file, unsigned long offset, const char *out_file);
// encode an entire file
// in_file: input filename containing raw data to be encoded
// out_file: output filename to write MIO0 compressed data to
int mio0_encode_file(const char *in_file, const char *out_file);
#endif // LIBMIO0_H_

View File

@ -0,0 +1,153 @@
#ifndef UTILS_H_
#define UTILS_H_
#include <stdio.h>
// defines
// printing size_t varies by compiler
#if defined(_MSC_VER) || defined(__MINGW32__)
#define SIZE_T_FORMAT "%Iu"
#else
#define SIZE_T_FORMAT "%zu"
#endif
#define KB 1024
#define MB (1024 * KB)
// number of elements in statically declared array
#define DIM(S_ARR_) (sizeof(S_ARR_) / sizeof(S_ARR_[0]))
#define MIN(A_, B_) ((A_) < (B_) ? (A_) : (B_))
#define MAX(A_, B_) ((A_) > (B_) ? (A_) : (B_))
// align value to N-byte boundary
#define ALIGN(VAL_, ALIGNMENT_) (((VAL_) + ((ALIGNMENT_) - 1)) & ~((ALIGNMENT_) - 1))
// read/write u32/16 big/little endian
#define read_u32_be(buf) (unsigned int)(((buf)[0] << 24) + ((buf)[1] << 16) + ((buf)[2] << 8) + ((buf)[3]))
#define read_u32_le(buf) (unsigned int)(((buf)[1] << 24) + ((buf)[0] << 16) + ((buf)[3] << 8) + ((buf)[2]))
#define write_u32_be(buf, val) do { \
(buf)[0] = ((val) >> 24) & 0xFF; \
(buf)[1] = ((val) >> 16) & 0xFF; \
(buf)[2] = ((val) >> 8) & 0xFF; \
(buf)[3] = (val) & 0xFF; \
} while(0)
#define read_u16_be(buf) (((buf)[0] << 8) + ((buf)[1]))
#define write_u16_be(buf, val) do { \
(buf)[0] = ((val) >> 8) & 0xFF; \
(buf)[1] = ((val)) & 0xFF; \
} while(0)
// print nibbles and bytes
#define fprint_nibble(FP, NIB_) fputc((NIB_) < 10 ? ('0' + (NIB_)) : ('A' + (NIB_) - 0xA), FP)
#define fprint_byte(FP, BYTE_) do { \
fprint_nibble(FP, (BYTE_) >> 4); \
fprint_nibble(FP, (BYTE_) & 0x0F); \
} while(0)
#define print_nibble(NIB_) fprint_nibble(stdout, NIB_)
#define print_byte(BYTE_) fprint_byte(stdout, BYTE_)
// Windows compatibility
#if defined(_MSC_VER) || defined(__MINGW32__)
#include <direct.h>
#define mkdir(DIR_, PERM_) _mkdir(DIR_)
#ifndef strcasecmp
#define strcasecmp(A, B) stricmp(A, B)
#endif
#endif
// typedefs
#define MAX_DIR_FILES 128
typedef struct
{
char *files[MAX_DIR_FILES];
int count;
} dir_list;
// global verbosity setting
extern int g_verbosity;
#define ERROR(...) fprintf(stderr, __VA_ARGS__)
#define INFO(...) if (g_verbosity) printf(__VA_ARGS__)
#define INFO_HEX(...) if (g_verbosity) print_hex(__VA_ARGS__)
// functions
// convert two bytes in big-endian to signed int
int read_s16_be(unsigned char *buf);
// convert four bytes in big-endian to float
float read_f32_be(unsigned char *buf);
// determine if value is power of 2
// returns 1 if val is power of 2, 0 otherwise
int is_power2(unsigned int val);
// print buffer as hex bytes
// fp: file pointer
// buf: buffer to read bytes from
// length: length of buffer to print
void fprint_hex(FILE *fp, const unsigned char *buf, int length);
void fprint_hex_source(FILE *fp, const unsigned char *buf, int length);
void print_hex(const unsigned char *buf, int length);
// perform byteswapping to convert from v64 to z64 ordering
void swap_bytes(unsigned char *data, long length);
// reverse endian to convert from n64 to z64 ordering
void reverse_endian(unsigned char *data, long length);
// get size of file without opening it;
// returns file size or negative on error
long filesize(const char *file_name);
// update file timestamp to now, creating it if it doesn't exist
void touch_file(const char *filename);
// read entire contents of file into buffer
// returns file size or negative on error
long read_file(const char *file_name, unsigned char **data);
// write buffer to file
// returns number of bytes written out or -1 on failure
long write_file(const char *file_name, unsigned char *data, long length);
// generate an output file name from input name by replacing file extension
// in_name: input file name
// out_name: buffer to write output name in
// extension: new file extension to use
void generate_filename(const char *in_name, char *out_name, char *extension);
// extract base filename from file path
// name: path to file
// returns just the file name after the last '/'
char *basename(const char *name);
// make a directory if it doesn't exist
// dir_name: name of the directory
void make_dir(const char *dir_name);
// copy a file from src_name to dst_name. will not make directories
// src_name: source file name
// dst_name: destination file name
long copy_file(const char *src_name, const char *dst_name);
// list a directory, optionally filtering files by extension
// dir: directory to list files in
// extension: extension to filter files by (NULL if no filtering)
// list: output list and count
void dir_list_ext(const char *dir, const char *extension, dir_list *list);
// free associated date from a directory list
// list: directory list filled in by dir_list_ext() call
void dir_list_free(dir_list *list);
// determine if a string ends with another string
// str: string to check if ends with 'suffix'
// suffix: string to see if 'str' ends with
// returns 1 if 'str' ends with 'suffix'
int str_ends_with(const char *str, const char *suffix);
#endif // UTILS_H_