1
0
mirror of https://github.com/ScrelliCopter/tmx2gba.git synced 2025-02-21 03:29:25 +11:00

18 Commits

Author SHA1 Message Date
b9c56ce5a7 actions: fix windows & universal artifacts 2024-03-22 15:17:47 +11:00
0b635ebe87 actions: convert to matrix 2024-03-22 14:41:44 +11:00
5d5dda81c9 rewrite assembly writer 2024-03-21 10:14:17 +11:00
417bc11fae clang warning pass 2024-03-21 08:21:02 +11:00
696057b5e6 correctness 2024-03-21 07:53:01 +11:00
17de8ac3ec first pass at splitting writer logic 2024-03-21 06:54:24 +11:00
db1de4ba8e update gitignore 2024-03-21 05:07:54 +11:00
f4930668ee fix accidental constexpr 2024-03-21 05:02:07 +11:00
5e466598ea update & fix ci (hopefully) 2024-03-21 05:00:02 +11:00
e2a69bf433 Merge branch 'master' into argparse
# Conflicts:
#	CMakeLists.txt
#	src/tmx2gba.cpp
2024-03-21 04:52:03 +11:00
d59fb39857 cut a versioned release before making potentially breaking changes 2024-03-21 04:38:23 +11:00
57455e0b73 msvc fix 2024-03-20 07:50:40 +11:00
c3bbe8135d argparse refactor 2024-03-20 07:29:29 +11:00
5c164b239d rewrite argument parsing 2024-03-19 14:39:49 +11:00
a222235605 normalise guards and copyright names in headers 2023-09-17 00:59:19 +10:00
6050224a65 uncringe comments somewhat 2023-09-15 14:14:04 +10:00
c351da76d1 Break out body of licence text 2023-09-15 14:14:04 +10:00
c65a607b61 option for building with asan 2023-09-15 14:14:04 +10:00
21 changed files with 937 additions and 1395 deletions

View File

@@ -2,36 +2,46 @@ name: CMake
on: on:
push: push:
branches: [ "master" ]
pull_request: pull_request:
branches: [ "master" ] branches: [ "master" ]
env: env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) ARTIFACT_NAME: tmx2gba
BUILD_TYPE: Release BUILD_TYPE: RelWithDebInfo
jobs: jobs:
build: build:
# The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. strategy:
# You can convert this to a matrix build if you need cross-platform coverage. matrix:
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix config:
runs-on: ubuntu-latest - { name: "MacOS 13.0 Universal", os: macos-13, artifact: macos-universal, arch: arm64;x86_64 }
- { name: "Windows MSVC x86", os: windows-latest, artifact: windows-x86, arch: x86 }
- { name: "Windows MSVC x64", os: windows-latest, artifact: windows-x64 }
- { name: "Windows MSVC ARM64", os: windows-latest, artifact: windows-arm64, arch: amd64_arm64 }
- { name: "Ubuntu", artifact: "linux", os: ubuntu-latest }
runs-on: ${{matrix.config.os}}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
# Since ninja isn't used it will take less time if this step is skipped
- uses: lukka/get-cmake@latest
if: ${{!startsWith(matrix.config.os, 'windows')}}
- uses: TheMrMilchmann/setup-msvc-dev@v3
if: ${{startsWith(matrix.config.os, 'windows')}}
with:
arch: ${{matrix.config.arch && matrix.config.arch || 'x64'}}
- name: Configure CMake - name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. run: >-
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type cmake -B build
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -G "${{startsWith(matrix.config.os, 'windows') && 'NMake Makefiles' || 'Ninja'}}"
${{(startsWith(matrix.config.os, 'macos') && matrix.config.arch) && format('-DCMAKE_OSX_ARCHITECTURES="{0}"', matrix.config.arch) || ''}}
${{matrix.config.extra}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build - name: Build
# Build your program with the given configuration run: cmake --build build --config ${{env.BUILD_TYPE}}
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Test
working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest -C ${{env.BUILD_TYPE}}
- uses: actions/upload-artifact@v4
with:
name: ${{env.ARTIFACT_NAME}}-${{matrix.config.artifact}}
path: build/src/${{env.ARTIFACT_NAME}}${{startsWith(matrix.config.os, 'windows') && '.exe' || ''}}

15
.gitignore vendored
View File

@@ -1,23 +1,13 @@
# Compiled Object files # Compiled Object files
*.slo
*.lo
*.o *.o
*.obj *.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries # Compiled Dynamic libraries
*.so *.so
*.dylib *.dylib
*.dll *.dll
# Fortran module files
*.mod
# Compiled Static libraries # Compiled Static libraries
*.lai
*.la *.la
*.a *.a
*.lib *.lib
@@ -41,6 +31,11 @@ Release/
# CMake Rubbish # CMake Rubbish
build/ build/
cmake-build-*/ cmake-build-*/
xcode/
# OS Rubbish
.DS_Store
Thumbs.db
# Some files I used for testing # Some files I used for testing
*.tmx *.tmx

View File

@@ -1,26 +1,19 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR) cmake_minimum_required(VERSION "3.15" FATAL_ERROR)
project(tmx2gba) project(tmx2gba VERSION "0.3")
# Options # Options
option(TMX2GBA_DKP_INSTALL "Install into DEVKITPRO prefix" OFF) option(TMX2GBA_DKP_INSTALL "Install into DEVKITPRO prefix" OFF)
option(ASAN "Enable address sanitiser" OFF)
# C++11 & C99 if (ASAN)
set(CMAKE_CXX_STANDARD 11) add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
set(CMAKE_C_STANDARD 99) add_link_options(-fsanitize=address -shared-libasan)
# Enable strong warnings
if (MSVC)
string(REPLACE "/W3" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
string(REPLACE "/W3" "/W4" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
else()
add_compile_options(-Wall)
endif() endif()
# Libraries # Libraries
add_subdirectory(ext/base64) add_subdirectory(ext/base64)
add_subdirectory(ext/miniz) add_subdirectory(ext/miniz)
add_subdirectory(ext/rapidxml) add_subdirectory(ext/rapidxml)
add_subdirectory(ext/ultragetopt)
# Main tmx2gba sources # Main tmx2gba sources
add_subdirectory(src) add_subdirectory(src)

17
COPYING.txt Normal file
View File

@@ -0,0 +1,17 @@
Copyright (C) 2015-2024 a dinosaur
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -20,6 +20,7 @@ tmx2gba [-h] [-r offset] [-lyc name] [-p 0-15] <-i inpath> <-o outpath>
| Command | Required | Notes | | Command | Required | Notes |
|--------------|----------|-----------------------------------------------------------------------| |--------------|----------|-----------------------------------------------------------------------|
| -h | N/A | Display help & command info. | | -h | N/A | Display help & command info. |
| -v | No | Display version & quit. |
| -l (name) | No | Name of layer to use (default first layer in TMX). | | -l (name) | No | Name of layer to use (default first layer in TMX). |
| -y (name) | No | Layer for palette mappings. | | -y (name) | No | Layer for palette mappings. |
| -c (name) | No | Output a separate 8bit collision map of the specified layer. | | -c (name) | No | Output a separate 8bit collision map of the specified layer. |

View File

@@ -1,22 +0,0 @@
include(CheckIncludeFile)
include(CheckFunctionExists)
check_include_file(strings.h HAVE_STRINGS_H)
check_function_exists(strcasecmp HAVE_STRCASECMP)
check_function_exists(_stricmp HAVE__STRICMP)
check_function_exists(strncasecmp HAVE_STRNCASECMP)
check_function_exists(_strnicmp HAVE__STRNICMP)
add_library(ultragetopt
ultragetopt.c ultragetopt.h)
add_library(External::ultragetopt ALIAS ultragetopt)
target_include_directories(ultragetopt
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(ultragetopt PRIVATE
$<$<BOOL:${HAVE_STRINGS_H}>:HAVE_STRINGS_H=1>
$<$<BOOL:${HAVE_STRCASECMP}>:HAVE_STRCASECMP=1>
$<$<BOOL:${HAVE__STRICMP}>:HAVE__STRICMP=1>
$<$<BOOL:${HAVE_STRNCASECMP}>:HAVE_STRNCASECMP=1>
$<$<BOOL:${HAVE__STRNICMP}>:HAVE__STRNICMP=1>)

View File

@@ -1,826 +0,0 @@
/* Ultra-Getopt - A replacement for getopt() with support for many common
* extensions, MS-DOS formatted option strings, and much more.
*
* To use this library as a replacement for vendor-provided getopt() functions,
* define ULTRAGETOPT_REPLACE_GETOPT and include "ultragetopt.h" after the
* vendor-provided headers for getopt() functions.
*
* Copyright (C) 2007-2011, Kevin Locke <kevin@kevinlocke.name>
*
* 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 <assert.h>
#include <ctype.h> /* islower() isupper() tolower() toupper() */
#include <stdarg.h>
#include <stdio.h> /* fprintf() */
#include <stdlib.h> /* getenv() */
#include <string.h> /* strcmp(), strncmp(), strchr() */
#if HAVE_STRINGS_H
# include <strings.h> /* strcasecmp(), strncasecmp() */
#endif
#undef ULTRAGETOPT_REPLACE_GETOPT /* Protect against project-wide defines */
#include "ultragetopt.h"
/* Define replacements for missing functions */
#if !HAVE_STRCASECMP && HAVE__STRICMP
# define strcasecmp _stricmp
#endif
#if !HAVE_STRNCASECMP && HAVE__STRNICMP
# define strncasecmp _strnicmp
#endif
#if !HAVE_STRCHR && HAVE_INDEX
# define strchr index
#endif
/* Supported defines:
* ULTRAGETOPT_LIKE_BSD Behave like BSD getopt()
* ULTRAGETOPT_LIKE_DARWIN Behave like Darwin (Mac OS) getopt()
* ULTRAGETOPT_LIKE_GNU Behave like GNU getopt()
* ULTRAGETOPT_LIKE_POSIX Behave like POSIX definition of getopt()
*
* ULTRAGETOPT_ASSIGNSPACE Parse "-o value" as "value" rather than " value"
* Note: Only applicable when argv[x] == "-o value"
* Not for argv[x] == "-o" [x+1] == "value"
* ULTRAGETOPT_DEFAULTOPTOPT Set optopt to this value by default on each
* call to getopt()
* ULTRAGETOPT_HYPHENARG Accept -option -arg as -option with argument
* "-arg" rather than -option missing argument
* Note: A single "-" is always accepted as an
* argument
* ULTRAGETOPT_LONGOPTADJACENT Accept adjacent arguments to long options
* (e.g. --optionarg) based on first longest-match
* ULTRAGETOPT_OPTIONPERMUTE Permute options, do not stop at first non-option
* Behaves like GNU getopt where leading '+' or
* $POSIXLY_CORRECT both stop this @ runtime
* ULTRAGETOPT_SHORTOPTASSIGN Support -o=file syntax for short options
* ULTRAGETOPT_SEPARATEDOPTIONAL Accept separated optional arguments
* Parse -o file as -o with argument file
* ULTRAGETOPT_DOS_DASH Support - and -- options in ultragetopt*_dos()
* ULTRAGETOPT_BSD_ERRORS Print error messages matching BSD getopt
* ULTRAGETOPT_DARWIN_ERRORS Print error messages matching Darwin getopt
* ULTRAGETOPT_GNU_ERRORS Print error messages matching GNU getopt
* ULTRAGETOPT_NO_EATDASHDASH Do not increment optind when argv[optind] is --
* ULTRAGETOPT_NO_OPTIONALARG Do not support GNU "::" optional argument
* Always supported in *_long*()
* ULTRAGETOPT_NO_OPTIONASSIGN Do not support --option=value syntax
*/
#ifdef ULTRAGETOPT_LIKE_POSIX
# define ULTRAGETOPT_NO_OPTIONALARG
# define ULTRAGETOPT_NO_OPTIONASSIGN
# undef ULTRAGETOPT_NO_EATDASHDASH
# undef ULTRAGETOPT_ASSIGNSPACE
# undef ULTRAGETOPT_BSD_ERRORS
# undef ULTRAGETOPT_DARWIN_ERRORS
# undef ULTRAGETOPT_GNU_ERRORS
# undef ULTRAGETOPT_OPTIONPERMUTE
# undef ULTRAGETOPT_SHORTOPTASSIGN
#elif defined(ULTRAGETOPT_LIKE_GNU)
# define ULTRAGETOPT_GNU_ERRORS
# define ULTRAGETOPT_HYPHENARG
# define ULTRAGETOPT_OPTIONPERMUTE
# undef ULTRAGETOPT_ASSIGNSPACE
# undef ULTRAGETOPT_NO_OPTIONALARG
# undef ULTRAGETOPT_NO_OPTIONASSIGN
# undef ULTRAGETOPT_NO_EATDASHDASH
# undef ULTRAGETOPT_SHORTOPTASSIGN
# undef ULTRAGETOPT_SEPARATEOPTIONAL
# undef ULTRAGETOPT_LONGOPTADJACENT
#elif defined(ULTRAGETOPT_LIKE_BSD)
# define ULTRAGETOPT_BSD_ERRORS
# define ULTRAGETOPT_OPTIONPERMUTE
# define ULTRAGETOPT_DEFAULTOPTOPT '?'
# undef ULTRAGETOPT_ASSIGNSPACE
# undef ULTRAGETOPT_NO_OPTIONALARG
# undef ULTRAGETOPT_NO_OPTIONASSIGN
# undef ULTRAGETOPT_NO_EATDASHDASH
# undef ULTRAGETOPT_SHORTOPTASSIGN
# undef ULTRAGETOPT_SEPARATEOPTIONAL
# undef ULTRAGETOPT_LONGOPTADJACENT
#elif defined(ULTRAGETOPT_LIKE_DARWIN)
# define ULTRAGETOPT_DARWIN_ERRORS
# define ULTRAGETOPT_OPTIONPERMUTE
# undef ULTRAGETOPT_ASSIGNSPACE
# undef ULTRAGETOPT_NO_OPTIONALARG
# undef ULTRAGETOPT_NO_OPTIONASSIGN
# undef ULTRAGETOPT_SHORTOPTASSIGN
# undef ULTRAGETOPT_NO_EATDASHDASH
# undef ULTRAGETOPT_SEPARATEOPTIONAL
# undef ULTRAGETOPT_LONGOPTADJACENT
#endif
#ifdef ULTRAGETOPT_NO_OPTIONASSIGN
static const char *const unixassigners = "";
static const char *const dosassigners = ":";
#elif defined(ULTRAGETOPT_OPTIONSPACE)
static const char *const unixassigners = "= ";
static const char *const dosassigners = ":= ";
#else
static const char *const unixassigners = "=";
static const char *const dosassigners = ":=";
#endif
#ifdef ULTRAGETOPT_DOS_DASH
static const char *const unixleaders = "-";
static const char *const dosleaders = "/-";
#else
static const char *const unixleaders = "-";
static const char *const dosleaders = "/";
#endif
/* Flags for all variants of ultragetopt*() */
static const int getoptflags = 0
#ifdef ULTRAGETOPT_SEPARATEDOPTIONAL
| UGO_SEPARATEDOPTIONAL
#endif
#ifdef ULTRAGETOPT_SHORTOPTASSIGN
| UGO_SHORTOPTASSIGN
#endif
#ifdef ULTRAGETOPT_NO_EATDASHDASH
| UGO_NOEATDASHDASH
#endif
#ifdef ULTRAGETOPT_HYPHENARG
| UGO_HYPHENARG
#endif
;
#ifdef ULTRAGETOPT_GNU_ERRORS
static const char *const errorarg =
"%s: option `%.*s' doesn't allow an argument\n";
static const char *const errornoarg =
"%s: option `%.*s' requires an argument \n";
static const char *const erroropt =
"%s: unrecognized option `%.*s'\n";
static const char *const errorargc =
"%s: option `-%c' does not take an argument\n";
static const char *const errornoargc =
"%s: option requires an argument -- `-%c'\n";
static const char *const erroroptc =
"%s: invalid option -- %c\n";
#elif defined(ULTRAGETOPT_BSD_ERRORS)
static const char *const errorarg =
"%s: option doesn't take an argument -- %.*s\n";
static const char *const errornoarg =
"%s: option requires an argument -- %.*s\n";
static const char *const erroropt =
"%s: unknown option -- %.*s\n";
static const char *const errorargc =
"%s: option doesn't take an argument -- %c\n";
static const char *const errornoargc =
"%s: option requires an argument -- %c\n";
static const char *const erroroptc =
"%s: unknown option -- %c\n";
#elif defined(ULTRAGETOPT_DARWIN_ERRORS)
static const char *const errorarg =
"%s: option `%.*s' doesn't allow an argument\n"; /* with -- */
static const char *const errornoarg =
"%s: option `%.*s' requires an argument\n";
static const char *const erroropt =
"%s: unrecognized option `%.*s'\n"; /* with -- */
static const char *const errorargc =
"%s: option doesn't take an argument -- %c\n";
static const char *const errornoargc =
"%s: option requires an argument -- %c\n";
static const char *const erroroptc =
"%s: invalid option -- %c\n";
#else /* POSIX-like */
static const char *const errorarg =
"%s: option does not take an argument -- %.*s\n";
static const char *const errornoarg =
"%s: option requires an argument -- %.*s\n";
static const char *const erroropt =
"%s: illegal option -- %.*s\n";
static const char *const errorargc =
"%s: option does not take an argument -- %c\n";
static const char *const errornoargc =
"%s: option requires an argument -- %c\n";
static const char *const erroroptc =
"%s: illegal option -- %c\n";
#endif
/* Globals to match optarg, optind, opterr, optopt, optreset */
char *ultraoptarg = NULL;
int ultraoptind = 1;
int ultraopterr = 1;
int ultraoptreset = 0;
#ifdef ULTRAGETOPT_DEFAULTOPTOPT
int ultraoptopt = ULTRAGETOPT_DEFAULTOPTOPT -0;
#else
int ultraoptopt = 0;
#endif
static int ultraoptnum = 0; /* How many options of the current multi-option
argument have been processed? (e.g. -vvv) */
/* Add format error checking for gcc versions that support it */
#if defined(__GNUC__) && __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR > 6)
static void print_error(int flags, const char *template, ...)
__attribute__ ((format (printf, 2, 3)));
#endif
/* Print errors only if not suppressed */
static void print_error(int flags, const char *template, ...)
{
va_list ap;
va_start(ap, template);
if (ultraopterr != 0 && !(flags & UGO_NOPRINTERR))
vfprintf(stderr, template, ap);
va_end(ap);
}
/* Check if an argument string looks like an option string */
static inline int like_option(const char *arg, const char *optleaders)
{
return arg != NULL &&
arg[0] != '\0' && /* >= 2 characters long */
arg[1] != '\0' &&
strchr(optleaders, arg[0]) && /* Starts with optleader */
(arg[2] != '\0' || arg[0] != arg[1]); /* Not -- */
}
/* Check if an argument string looks like the option terminator string */
static inline int like_optterm(const char *arg, const char *optleaders)
{
return arg != NULL &&
arg[0] != '\0' &&
arg[1] != '\0' &&
arg[2] == '\0' &&
arg[0] == arg[1] &&
strchr(optleaders, arg[0]);
}
/* Check if an argument string looks like an option argument string */
static inline int like_optarg(const char *arg, const char *optleaders,
int allow_option)
{
return arg != NULL &&
(allow_option ||
(!like_option(arg, optleaders) && !like_optterm(arg, optleaders)));
}
/* If argv[curopt] matches a long option, return the index of that option
* Otherwise, return -1
* If it has an adjacent argument, return pointer to it in longarg, else NULL
*/
static int match_longopt(int curopt, char *const argv[],
const struct option *longopts, const char *assigners,
const char *optleaders, int flags, char **longarg)
{
size_t alen, optnamelen = 0;
char *optname;
char *temp;
int i;
int (*optncmp)(const char *s1, const char *s2, size_t n);
if (longarg == NULL)
longarg = &temp;
*longarg = NULL;
if (flags & UGO_CASEINSENSITIVE)
optncmp = strncasecmp;
else
optncmp = strncmp;
if (longopts == NULL)
return -1;
if (!like_option(argv[curopt], optleaders))
return -1;
if (flags & UGO_SINGLELEADERONLY) {
optname = argv[curopt]+1;
} else if (!strchr(optleaders, argv[curopt][1])) {
/* Possible short option */
if (flags & UGO_SINGLELEADERLONG)
optname = argv[curopt]+1;
else
return -1;
} else {
optname = argv[curopt]+2;
}
/* Do first longest-match search if requested */
if (flags & UGO_LONGOPTADJACENT) {
size_t matchlen = 0;
int matchind = -1;
for (i=0; longopts[i].name != NULL; i++) {
size_t longnamelen = strlen(longopts[i].name);
if (longnamelen > matchlen
&& optncmp(optname, longopts[i].name, longnamelen) == 0) {
matchind = i;
matchlen = longnamelen;
}
}
if (matchlen > 0) {
/* See if our match has an adjacent argument */
if (optname[matchlen] != '\0') {
/* Strip assigner character if present */
if (strchr(assigners, optname[matchlen]))
*longarg = optname+matchlen+1;
else
*longarg = optname+matchlen;
}
return matchind;
}
return -1;
}
/* Check for assigner in the option */
alen = strlen(assigners);
for (i=0; (unsigned)i < alen; i++) {
char *asn = strchr(optname, assigners[i]);
if (asn != NULL) {
optnamelen = asn - optname;
*longarg = asn+1;
break;
}
}
if (optnamelen == 0)
optnamelen = strlen(optname);
for (i=0; longopts[i].name != NULL; i++)
if (optncmp(optname, longopts[i].name, optnamelen) == 0
&& strlen(longopts[i].name) == optnamelen)
return i;
return -1;
}
/* Check if an option has a separate argument (in the following argv[] index) */
static int has_separate_argument(int curopt, int argc, char *const argv[],
const char *shortopts,
const struct option *longopts,
const char *assigners, const char *optleaders,
int flags)
{
int longind;
char *longarg;
assert(curopt < argc && like_option(argv[curopt], optleaders));
/* Check if we have a long option */
longind = match_longopt(ultraoptind, argv, longopts, assigners, optleaders,
flags, &longarg);
if (longind >= 0) {
if (longopts[longind].has_arg == no_argument
|| longarg != NULL
|| (longopts[longind].has_arg == optional_argument
&& !(flags & UGO_SEPARATEDOPTIONAL)))
return 0;
return like_optarg(argv[curopt+1], optleaders,
(flags & UGO_HYPHENARG) &&
longopts[longind].has_arg == required_argument);
} else if (!strchr(optleaders, argv[curopt][1])) {
/* Short option */
char *optpos;
optpos = strchr(shortopts, argv[curopt][1]);
if ((flags & UGO_CASEINSENSITIVE) && optpos == NULL) {
if (islower(argv[curopt][1]))
optpos = strchr(shortopts, toupper(argv[curopt][1]));
else
optpos = strchr(shortopts, tolower(argv[curopt][1]));
}
return optpos != NULL /* Option found */
&& optpos[1] == ':' /* Option takes argument */
&& (optpos[2] != ':' || (flags & UGO_SEPARATEDOPTIONAL))
&& argv[curopt][2] == '\0' /* Argument is not adjacent */
&& like_optarg(argv[curopt+1], /* Is an argument */
optleaders,
(flags & UGO_HYPHENARG) && optpos[2] != ':');
}
/* No match */
return 0;
}
/* Bring the next option, or terminator, up to ultraoptind if there is one
* Returns number of words shifted forward
*/
static int permute_options(int argc, char *argv[], const char *shortopts,
const struct option *longopts,
const char *assigners, const char *optleaders,
int flags)
{
int curopt = ultraoptind;
/* If we already have an option or no more possible, give up */
if (curopt >= argc || like_option(argv[curopt], optleaders))
return 0;
for ( ; curopt < argc && argv[curopt]; curopt++) {
int shiftarg = 0;
int i;
/* Permute options and the option terminator */
if (like_option(argv[curopt], optleaders)) {
/* Check if we need to shift argument too */
shiftarg = has_separate_argument(curopt, argc, argv, shortopts,
longopts, assigners, optleaders,
flags);
} else if (!like_optterm(argv[curopt], optleaders)) {
continue;
}
/* Shift option */
for (i=curopt; i>ultraoptind; i--) {
char *temp = argv[i];
argv[i] = argv[i-1];
argv[i-1] = temp;
if (shiftarg) {
temp = argv[i+1];
argv[i+1] = argv[i];
argv[i] = temp;
}
}
/* All done */
if (shiftarg)
return 2;
else
return 1;
}
/* Couldn't find an option, bummer */
return 0;
}
/* Handle a longopts[longind] matches argv[ultraoptind] actions */
static int handle_longopt(int longind, char *longarg, int noseparg,
char *const argv[],
const struct option *longopts, int *indexptr,
const char *optleaders, int flags)
{
/* Handle assignment arguments */
if (longarg && longopts[longind].has_arg == no_argument) {
print_error(flags, errorarg, argv[0],
longarg-argv[ultraoptind]-1, argv[ultraoptind]);
/* TODO: What is a good value to put in ultraoptopt? */
/* Looks like GNU getopt() uses val */
ultraoptopt = longopts[longind].val;
ultraoptind++;
return '?';
} else if (longarg) {
ultraoptind++;
ultraoptarg = longarg;
if (indexptr)
*indexptr = longind;
if (longopts[longind].flag) {
*(longopts[longind].flag) = longopts[longind].val;
return 0;
} else
return longopts[longind].val;
}
/* Handle missing required argument */
if (longopts[longind].has_arg == required_argument
&& (noseparg
|| !like_optarg(argv[ultraoptind+1],
optleaders,
flags & UGO_HYPHENARG))) {
print_error(flags, errornoarg, argv[0],
strlen(argv[ultraoptind]), argv[ultraoptind]);
ultraoptind++;
if (flags & UGO_MISSINGCOLON)
return ':';
else
return '?';
}
/* Handle available argument */
if ((longopts[longind].has_arg == required_argument
|| (longopts[longind].has_arg == optional_argument
&& (flags & UGO_SEPARATEDOPTIONAL)))
&& !noseparg
&& like_optarg(argv[ultraoptind+1],
optleaders,
(flags & UGO_HYPHENARG) &&
longopts[longind].has_arg == required_argument)) {
ultraoptarg = argv[ultraoptind+1];
ultraoptind += 2;
} else
ultraoptind++;
if (indexptr)
*indexptr = longind;
if (longopts[longind].flag) {
*(longopts[longind].flag) = longopts[longind].val;
return 0;
} else
return longopts[longind].val;
}
int ultragetopt_tunable(int argc, char *const argv[], const char *shortopts,
const struct option *longopts, int *indexptr,
const char *assigners, const char *optleaders,
int flags)
{
char *opt; /* Option we are processing */
char *optpos; /* Pointer to opt in shortopts */
int noseparg = 0; /* Force option not to have a separate argument */
if (ultraoptreset) {
ultraoptind = 1;
ultraopterr = 1;
ultraoptnum = 0;
ultraoptreset = 0;
}
ultraoptarg = NULL;
#ifdef ULTRAGETOPT_DEFAULTOPTOPT
ultraoptopt = ULTRAGETOPT_DEFAULTOPTOPT -0;
#endif
/* Sanity check (These are specified verbatim in SUS) */
if (ultraoptind > argc
|| argv[ultraoptind] == NULL)
return -1;
/* No permuting when $POSIXLY_CORRECT is set (to match GNU getopt) */
if (getenv("POSIXLY_CORRECT"))
flags &= ~UGO_OPTIONPERMUTE;
/* Get flags from shortopts */
for ( ; shortopts && *shortopts; shortopts++) {
if (*shortopts == '+')
flags &= ~UGO_OPTIONPERMUTE;
else if (*shortopts == '-')
flags |= UGO_NONOPTARG;
else if (*shortopts == ':') {
flags |= UGO_NOPRINTERR;
flags |= UGO_MISSINGCOLON;
} else
break;
}
/* Found non-option */
if (!like_option(argv[ultraoptind], optleaders)) {
int shifted;
if (like_optterm(argv[ultraoptind], optleaders)) {
if (!(flags & UGO_NOEATDASHDASH))
ultraoptind++;
return -1;
}
if (flags & UGO_NONOPTARG) {
ultraoptarg = argv[ultraoptind];
ultraoptind++;
return 1;
}
if (!(flags & UGO_OPTIONPERMUTE))
return -1;
shifted = permute_options(argc, (char **)argv, shortopts, longopts,
assigners, optleaders, flags);
if (shifted == 0)
return -1;
else if (shifted == 1)
noseparg = 1;
if (like_optterm(argv[ultraoptind], optleaders)) {
if (!(flags & UGO_NOEATDASHDASH))
ultraoptind++;
return -1;
}
}
/* At this point we must have an option of some sort */
assert(like_option(argv[ultraoptind], optleaders));
/* Handle --* */
if (argv[ultraoptind][0] == argv[ultraoptind][1]) {
int longind;
char *longarg;
/* Handle long option */
longind = match_longopt(ultraoptind, argv, longopts, assigners,
optleaders, flags, &longarg);
if (longind < 0) {
if (longarg == NULL)
print_error(flags, erroropt, argv[0],
strlen(argv[ultraoptind]), argv[ultraoptind]);
else
print_error(flags, erroropt, argv[0],
longarg - argv[ultraoptind] - 1, argv[ultraoptind]);
/* TODO: What is a good value for optopt in this case? */
/* Looks like BSD uses 0 */
ultraoptopt = 0;
ultraoptind++;
return '?';
}
return handle_longopt(longind, longarg, noseparg, argv,
longopts, indexptr, optleaders, flags);
}
/* See if it matches a long-only option */
if (longopts != NULL &&
ultraoptnum == 0 &&
((flags & UGO_SINGLELEADERLONG) ||
(flags & UGO_SINGLELEADERONLY))) {
int longind;
char *longarg;
longind = match_longopt(ultraoptind, argv, longopts, assigners,
optleaders, flags, &longarg);
if (longind >= 0)
return handle_longopt(longind, longarg, noseparg, argv,
longopts, indexptr, optleaders, flags);
}
/* No long matches, process short option */
opt = argv[ultraoptind] + ultraoptnum + 1;
optpos = strchr(shortopts, opt[0]);
if (optpos == NULL && (flags & UGO_CASEINSENSITIVE)) {
if (islower(opt[0]))
optpos = strchr(shortopts, toupper(opt[0]));
else
optpos = strchr(shortopts, tolower(opt[0]));
}
/* This could indicate ultraoptnum not being reset properly */
assert(opt[0] != '\0');
/* Check for invalid or unrecognized option */
if (optpos == NULL || opt[0] == ':') {
print_error(flags, erroroptc, argv[0], opt[0]);
ultraoptopt = opt[0];
if (opt[1] != '\0')
ultraoptnum++;
else {
ultraoptnum = 0;
ultraoptind++;
}
return '?';
}
/* Handle arguments */
if (optpos[1] == ':') {
ultraoptnum = 0;
/* Handle adjacent arguments -ofile.txt */
if (opt[1] != '\0') {
/* Skip over assignment character */
if ((flags & UGO_SHORTOPTASSIGN) && strchr(assigners, opt[1]))
ultraoptarg = opt + 2;
else
ultraoptarg = opt + 1;
ultraoptind++;
return optpos[0];
}
/* Handle optional argument not present */
if ((flags & UGO_OPTIONALARG) /* accept optionals */
&& optpos[2] == ':' /* opt takes optional */
&& (argv[ultraoptind+1] == NULL /* optional doesn't exist */
|| !(flags & UGO_SEPARATEDOPTIONAL) /* separated not accepted */
|| like_option(argv[ultraoptind+1], optleaders))) {
ultraoptind++;
return optpos[0];
}
/* Handle separated argument missing */
if (ultraoptind+2 > argc
|| noseparg
|| !like_optarg(argv[ultraoptind+1],
optleaders,
(flags & UGO_HYPHENARG))) {
ultraoptind++;
print_error(flags, errornoargc, argv[0], opt[0]);
ultraoptopt = opt[0];
if (flags & UGO_MISSINGCOLON)
return ':';
else
return '?';
}
ultraoptind += 2;
ultraoptarg = argv[ultraoptind-1];
return optpos[0];
}
/* Handle argumentless option with assigned option */
if ((flags & UGO_SHORTOPTASSIGN)
&& opt[1] != '\0' && strchr(assigners, opt[1])) {
print_error(flags, errorargc, argv[0], opt[0]);
ultraoptnum = 0;
ultraoptopt = opt[0];
ultraoptind++;
return '?';
}
if (opt[1] != '\0') {
ultraoptnum++;
} else {
ultraoptnum = 0;
ultraoptind++;
}
return optpos[0];
}
/* POSIX-compliant getopt
*
* Handles optional argument '::' specifier as an extension for compatibility
* with glibc
*/
int ultragetopt(int argc, char * const argv[], const char *optstring)
{
int flags = getoptflags;
#ifdef ULTRAGETOPT_OPTIONPERMUTE
flags |= UGO_OPTIONPERMUTE;
#endif
#ifndef ULTRAGETOPT_NO_OPTIONALARG
flags |= UGO_OPTIONALARG;
#endif
return ultragetopt_tunable(argc, argv, optstring, NULL, NULL,
unixassigners, unixleaders, flags);
}
/* GNU getopt_long workalike
*
* Argument reordering not yet implemented
* Leading + and - under consideration (behavior violates POSIX...)
*/
int ultragetopt_long(int argc, char *const argv[], const char *shortopts,
const struct option *longopts, int *indexptr)
{
return ultragetopt_tunable(argc, argv, shortopts, longopts, indexptr,
unixassigners, unixleaders,
getoptflags | UGO_OPTIONPERMUTE | UGO_OPTIONALARG);
}
/* GNU getopt_long_only workalike */
int ultragetopt_long_only(int argc, char *const argv[], const char *shortopts,
const struct option *longopts, int *indexptr)
{
return ultragetopt_tunable(argc, argv, shortopts, longopts, indexptr,
unixassigners, unixleaders,
getoptflags | UGO_SINGLELEADERLONG
| UGO_OPTIONPERMUTE | UGO_OPTIONALARG);
}
int ultragetopt_dos(int argc, char * const argv[], const char *optstring)
{
return ultragetopt_tunable(argc, argv, optstring, NULL, NULL,
dosassigners, dosleaders,
getoptflags | UGO_CASEINSENSITIVE);
}
int ultragetopt_long_dos(int argc, char *const argv[], const char *shortopts,
const struct option *longopts, int *indexptr)
{
return ultragetopt_tunable(argc, argv, shortopts, longopts, indexptr,
dosassigners, dosleaders,
getoptflags | UGO_CASEINSENSITIVE
| UGO_SINGLELEADERLONG | UGO_SINGLELEADERONLY
| UGO_OPTIONPERMUTE | UGO_OPTIONALARG);
}
/* vim:set sts=4 sw=4: */

View File

@@ -1,105 +0,0 @@
/* define ULTRAGETOPT_REPLACE_GETOPT for ultragetopt*() to replace getopt*() */
/* define ULTRAGETOPT_ONLY_DOS for ultragetopt*_dos() to replace ultragetopt*() */
#ifndef INCLUDED_GETOPT_H
#define INCLUDED_GETOPT_H 1
#ifdef __cplusplus
extern "C" {
#endif
#define ULTRAGETOPT_REPLACE_GETOPT
#define ULTRAGETOPT_LIKE_GNU
#define ULTRAGETOPT_LINKAGE extern
/* Flag values to pass to getopt_tunable() */
#define UGO_CASEINSENSITIVE 0x1
#define UGO_SINGLELEADERLONG 0x2
#define UGO_OPTIONPERMUTE 0x4
#define UGO_NONOPTARG 0x8
#define UGO_NOPRINTERR 0x10
#define UGO_OPTIONALARG 0x20
#define UGO_MISSINGCOLON 0x40
#define UGO_SEPARATEDOPTIONAL 0x80
#define UGO_SHORTOPTASSIGN 0x100
#define UGO_NOEATDASHDASH 0x200
#define UGO_LONGOPTADJACENT 0x400
#define UGO_HYPHENARG 0x800
#define UGO_SINGLELEADERONLY 0x1000
#ifndef required_argument
# define no_argument 0
# define required_argument 1
# define optional_argument 2
struct option {
const char *name; /* Name of the option */
int has_arg; /* Does the option take an argument? */
int *flag; /* Location to store val when option encountered */
int val; /* Value to return (or store in flag) for option */
};
#endif /* required_argument */
ULTRAGETOPT_LINKAGE char *ultraoptarg;
ULTRAGETOPT_LINKAGE int ultraoptind, ultraopterr, ultraoptopt, ultraoptreset;
ULTRAGETOPT_LINKAGE int ultragetopt(int argc, char *const argv[],
const char *optstring);
ULTRAGETOPT_LINKAGE int ultragetopt_long(int argc, char *const argv[],
const char *shortopts, const struct option *longopts, int *indexptr);
ULTRAGETOPT_LINKAGE int ultragetopt_long_only(int argc, char *const argv[],
const char *shortopts, const struct option *longopts, int *indexptr);
ULTRAGETOPT_LINKAGE int ultragetopt_dos(int argc, char * const argv[],
const char *optstring);
ULTRAGETOPT_LINKAGE int ultragetopt_long_dos(int argc, char *const argv[],
const char *shortopts, const struct option *longopts, int *indexptr);
/* Getopt with modifiable (tunable) behavior - also the backend for all other
* getopt functions.
* assigners - string of characters accepted to assign to an option
* (e.g. --outfile=file.txt where '=' is the assigner)
* optleaders - string of characters that indicate an option
* (usually "-" on UNIX, "/" on DOS)
* flags - see README for list of accepted flags
*/
ULTRAGETOPT_LINKAGE int ultragetopt_tunable(int argc, char *const argv[],
const char *shortopts, const struct option *longopts, int *indexptr,
const char *assigners, const char *optleaders, int flags);
#ifdef ULTRAGETOPT_REPLACE_GETOPT
# define optarg ultraoptarg
# define optind ultraoptind
# define opterr ultraopterr
# define optopt ultraoptopt
# define optreset ultraoptreset
# define getopt(argc, argv, optstring) \
ultragetopt(argc, argv, optstring)
# define getopt_long(argc, argv, shortopts, longopts, indexptr) \
ultragetopt_long(argc, argv, shortopts, longopts, indexptr)
# define getopt_long_only(argc, argv, shortopts, longopts, indexptr) \
ultragetopt_long_only(argc, argv, shortopts, longopts, indexptr)
# define getopt_dos(argc, argv, optstring) \
ultragetopt_dos(argc, argv, optstring)
# define getopt_long_dos(argc, argv, shortopts, longopts, indexptr) \
ultragetopt_long_dos(argc, argv, shortopts, longopts, indexptr)
#endif /* GETOPT_NO_EXTENSIONS */
#ifdef ULTRAGETOPT_DOS_ONLY
# define ultragetopt(argc, argv, optstring) \
ultragetopt_dos(argc, argv, optstring)
# define ultragetopt_long(argc, argv, shortopts, longopts, indexptr) \
ultragetopt_long_dos(argc, argv, shortopts, longopts, indexptr)
# define ultragetopt_long_only(argc, argv, shortopts, longopts, indexptr) \
ultragetopt_long_dos(argc, argv, shortopts, longopts, indexptr)
#endif /* ULTRAGETOPT_DOS_ONLY */
#ifdef __cplusplus
}
#endif
#endif /* INCLUDED_GETOPT_H */

View File

@@ -1,14 +1,28 @@
add_executable(tmx2gba add_executable(tmx2gba
tmx2gba.cpp argparse.hpp argparse.cpp
tmxlayer.hpp tmxlayer.hpp
tmxobject.hpp tmxobject.hpp
tmxreader.hpp tmxreader.cpp tmxreader.hpp tmxreader.cpp
tmxtileset.hpp) tmxtileset.hpp
swriter.hpp swriter.cpp
headerwriter.hpp headerwriter.cpp
tmx2gba.cpp)
set_target_properties(tmx2gba PROPERTIES
# C++20 & C99
CXX_STANDARD 20
C_STANDARD 99)
# Enable strong warnings
target_compile_options(tmx2gba PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/Wall>
$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-Wall -Wextra -pedantic>
$<$<CXX_COMPILER_ID:Clang,AppleClang>:-Weverything -Wno-c++98-compat>)
target_link_libraries(tmx2gba target_link_libraries(tmx2gba
External::base64 External::base64
External::miniz External::miniz
External::rapidxml External::rapidxml)
External::ultragetopt)
if (TMX2GBA_DKP_INSTALL) if (TMX2GBA_DKP_INSTALL)
if (DEFINED ENV{DEVKITPRO}) if (DEFINED ENV{DEVKITPRO})

216
src/argparse.cpp Normal file
View File

@@ -0,0 +1,216 @@
/* argparse.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
#include "argparse.hpp"
#include <optional>
#include <cstring>
#include <filesystem>
#include <iomanip>
#include <iostream>
ArgParse::ArgParser::ArgParser(
const std::string_view argv0,
Options options,
HandleOption&& handler
) noexcept :
name(std::filesystem::path(argv0).filename().string()),
options(options),
handler(std::forward<HandleOption>(handler)) {}
void ArgParse::Options::ShowShortUsage(const std::string_view name, std::ostream& out) const
{
out << "Usage: " << name;
for (const auto& it : options)
{
if (it.argumentName)
{
// Option with argument
it.required
? out << " <-" << it.flag << ' ' << it.argumentName << '>'
: out << " [-" << it.flag << ' ' << it.argumentName << ']';
}
else
{
// Argument-less flag
it.required
? out << " <-" << it.flag << '>'
: out << " [-" << it.flag << ']';
}
}
out << std::endl;
}
void ArgParse::Options::ShowHelpUsage(const std::string_view name, std::ostream& out) const
{
// Base usage
out << "Usage: " << name << " [-";
for (const auto& it : options)
if (!it.required)
out << it.flag;
out << "] <-";
for (const auto& it : options)
if (it.required)
out << it.flag;
out << ">" << std::endl;
// Determine the alignment width from the longest argument
auto paramLength = [](const Option& p) -> int { return p.argumentName
? static_cast<int>(std::strlen(p.argumentName) + 3)
: 1; };
auto longestParam = std::max_element(options.begin(), options.end(),
[=](auto a, auto b) -> bool { return paramLength(a) < paramLength(b); });
auto alignWidth = paramLength(*longestParam) + 3;
// print argument descriptions
for (const auto& it : options)
{
auto decorateArgument = [=] { return " <" + std::string(it.argumentName) + "> "; };
out << " -" << it.flag
<< std::left << std::setw(alignWidth) << std::setfill('-') << (it.argumentName ? decorateArgument() : " ")
<< " " << it.helpString << std::endl;
}
out << std::flush;
}
ArgParse::ParseCtrl ArgParse::ParserState::Next(const std::string_view token)
{
auto getFlag = [](const std::string_view s) { return s[0] == '-' && s[1] ? std::optional<int>(s[1]) : std::nullopt; };
auto getOption = [&](int flag) -> std::optional<std::reference_wrapper<const Option>>
{
for (auto& opt : options.options)
if (opt.flag == flag)
return std::optional(std::cref(opt));
return {};
};
if (expectArg)
{
expectArg = false;
return handler(flagChar, token);
}
else
{
auto flag = getFlag(token);
if (flag.has_value())
{
flagChar = flag.value();
const auto opt = getOption(flagChar);
if (opt.has_value())
{
bool expect = opt.value().get().argumentName != nullptr;
if (token.length() <= 2)
{
expectArg = expect;
if (!expectArg)
return handler(flagChar, "");
}
else
{
return handler(flagChar, expect ? token.substr(2) : "");
}
}
}
else if (!token.empty())
{
return ParseCtrl::QUIT_ERR_UNEXPECTED;
}
}
return ParseCtrl::CONTINUE;
}
void ArgParse::ArgParser::DisplayError(const std::string_view message, bool helpPrompt) const
{
std::cerr << GetName() << ": " << message << std::endl;
options.ShowShortUsage(GetName(), std::cerr);
if (helpPrompt)
std::cerr << "Run '" << GetName() << " -h' to view all available options." << std::endl;
}
bool ArgParse::ArgParser::CheckParse(ArgParse::ParseErr err) const
{
switch (err)
{
case ParseErr::OK:
return true;
case ParseErr::OPT_UNKNOWN:
DisplayError("Unrecognised option.");
return false;
case ParseErr::UNEXPECTED:
DisplayError("Unexpected token.");
return false;
case ParseErr::ARG_EXPECTED:
DisplayError("Requires an argument.");
return false;
case ParseErr::ARG_INVALID:
DisplayError("Invalid argument.", false);
return false;
case ParseErr::ARG_RANGE:
DisplayError("Argument out of range.", false);
return false;
}
}
bool ArgParse::ReadParamFile(std::vector<std::string>& tokens, std::istream& file)
{
bool inQuote = false;
std::string quoteStr;
const auto store = [&](const std::string_view token, bool quote)
{
if (quote)
quoteStr = token;
else
tokens.emplace_back(token);
};
while (!file.eof())
{
if (!inQuote)
{
std::string token;
file >> token;
if (!token.empty())
{
std::string::size_type beg = 0, end;
while ((end = token.find_first_of('"', beg)) != std::string::npos)
{
auto size = end - beg;
if (size > 0)
store(token.substr(beg, size), !inQuote);
inQuote = !inQuote;
beg = end + 1;
}
if (beg > 0)
{
auto size = token.length() - beg;
if (size > 0)
token = token.substr(beg);
else
token.clear();
}
if (!token.empty())
store(token, inQuote);
}
}
else
{
const int c = file.get();
if (c == '"')
{
tokens.emplace_back(quoteStr);
quoteStr.clear();
inQuote = false;
}
else
{
quoteStr.push_back(static_cast<char>(c));
}
}
}
return !inQuote;
}

132
src/argparse.hpp Normal file
View File

@@ -0,0 +1,132 @@
/* argparse.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
#ifndef ARGPARSE_HPP
#define ARGPARSE_HPP
#include <initializer_list>
#include <functional>
#include <string>
#include <ostream>
#include <span>
#include <ranges>
#include <string_view>
#include <vector>
namespace ArgParse
{
struct Option
{
const char* argumentName;
const char* helpString;
char flag;
bool required;
static constexpr Option Optional(char flag, const char* name, const char* help)
{
return { name, help, flag, false };
}
static constexpr Option Required(char flag, const char* name, const char* help)
{
return { name, help, flag, false };
}
};
struct Options
{
const std::vector<Option> options;
inline Options(const std::initializer_list<Option>&& rhs) : options(rhs) {}
void ShowShortUsage(const std::string_view name, std::ostream& out) const;
void ShowHelpUsage(const std::string_view name, std::ostream& out) const;
};
enum class ParseCtrl
{
CONTINUE,
QUIT_EARLY,
QUIT_ERR_UNKNOWN,
QUIT_ERR_UNEXPECTED,
QUIT_ERR_EXPECTARG,
QUIT_ERR_INVALID,
QUIT_ERR_RANGE
};
enum class ParseErr
{
OK,
OPT_UNKNOWN,
UNEXPECTED,
ARG_EXPECTED,
ARG_INVALID,
ARG_RANGE
};
using HandleOption = std::function<ParseCtrl(int, const std::string_view)>;
class ParserState
{
HandleOption handler;
const Options& options;
int flagChar;
bool expectArg = false;
public:
ParserState(HandleOption handler, const Options& options) noexcept
: handler(handler), options(options) {}
[[nodiscard]] bool ExpectingArg() const { return expectArg; }
[[nodiscard]] ParseCtrl Next(const std::string_view token);
};
class ArgParser
{
const std::string name;
Options options;
HandleOption handler;
[[nodiscard]] bool CheckParse(ArgParse::ParseErr err) const;
public:
explicit ArgParser(const std::string_view argv0, Options options, HandleOption&& handler) noexcept;
[[nodiscard]] const std::string_view GetName() const noexcept { return name; }
void DisplayError(const std::string_view message, bool helpPrompt = true) const;
template <typename V>
[[nodiscard]] bool Parse(V args)
{
ParserState state(handler, options);
for (auto arg : args)
{
ParseErr err = ParseErr::UNEXPECTED;
switch (state.Next(arg))
{
case ParseCtrl::CONTINUE: continue;
case ParseCtrl::QUIT_EARLY: err = ParseErr::OK; break;
case ParseCtrl::QUIT_ERR_UNKNOWN: err = ParseErr::OPT_UNKNOWN; break;
case ParseCtrl::QUIT_ERR_UNEXPECTED: err = ParseErr::UNEXPECTED; break;
case ParseCtrl::QUIT_ERR_EXPECTARG: err = ParseErr::ARG_EXPECTED; break;
case ParseCtrl::QUIT_ERR_INVALID: err = ParseErr::ARG_INVALID; break;
case ParseCtrl::QUIT_ERR_RANGE: err = ParseErr::ARG_RANGE; break;
}
if (!CheckParse(err))
return false;
}
return CheckParse(state.ExpectingArg() ? ParseErr::ARG_EXPECTED : ParseErr::OK);
}
[[nodiscard]] inline bool Parse(std::initializer_list<std::string_view> args)
{
return Parse<std::initializer_list<std::string_view>>(args);
}
[[nodiscard]] inline bool Parse(std::span<char*> args)
{
return Parse(args | std::views::transform([](char const* v){ return std::string_view(v); }));
}
};
[[nodiscard]] extern bool ReadParamFile(std::vector<std::string>& tokens, std::istream& file);
}
#endif//ARGPARSE_HPP

93
src/headerwriter.cpp Normal file
View File

@@ -0,0 +1,93 @@
/* headerwriter.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
#include "headerwriter.hpp"
template <typename T> static constexpr std::string_view DatType();
template <> constexpr std::string_view DatType<uint8_t>() { return "unsigned char"; }
template <> constexpr std::string_view DatType<uint16_t>() { return "unsigned short"; }
template <> constexpr std::string_view DatType<uint32_t>() { return "unsigned int"; }
void HeaderWriter::WriteSize(int width, int height)
{
stream << std::endl;
WriteDefine(mName + "Width", width);
WriteDefine(mName + "Height", height);
}
void HeaderWriter::WriteCharacterMap(const std::span<uint16_t> charData)
{
stream << std::endl;
WriteDefine(mName + "TilesLen", charData.size() * 2);
WriteSymbol(mName + "Tiles", DatType<uint16_t>(), charData.size());
}
void HeaderWriter::WriteCollision(const std::span<uint8_t> collisionData)
{
stream << std::endl;
WriteDefine(mName + "CollisionLen", collisionData.size());
WriteSymbol(mName + "Collision", DatType<uint8_t>(), collisionData.size());
}
void HeaderWriter::WriteObjects(const std::span<uint32_t> objData)
{
stream << std::endl;
WriteDefine(mName + "ObjCount", objData.size() / 3);
WriteDefine(mName + "ObjdatLen", objData.size() * sizeof(int));
WriteSymbol(mName + "Objdat", DatType<uint32_t>(), objData.size());
}
static std::string GuardName(const std::string_view name)
{
auto upper = std::string(name);
for (auto& c: upper)
c = static_cast<char>(toupper(c));
return "TMX2GBA_" + upper;
}
void HeaderWriter::WriteGuardStart()
{
const std::string guard = GuardName(mName);
stream << "#ifndef " << guard << std::endl;
stream << "#define " << guard << std::endl;
}
void HeaderWriter::WriteGuardEnd()
{
const std::string guard = GuardName(mName);
stream << std::endl << "#endif//" << guard << std::endl;
}
HeaderWriter::~HeaderWriter()
{
if (stream.is_open())
{
WriteGuardEnd();
stream.close();
}
}
bool HeaderWriter::Open(const std::filesystem::path& path, const std::string_view name)
{
mName = name;
stream.open(path);
if (!stream.is_open())
return false;
WriteGuardStart();
return true;
}
void HeaderWriter::WriteDefine(const std::string_view name, const std::string_view value)
{
stream << "#define " << name << " " << value << std::endl;
}
void HeaderWriter::WriteSymbol(const std::string_view name, const std::string_view type, std::size_t count)
{
stream << "extern const " << type << " " << name << "[" << count << "];" << std::endl;
}

46
src/headerwriter.hpp Normal file
View File

@@ -0,0 +1,46 @@
/* headerwriter.hpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
#ifndef HEADERWRITER_HPP
#define HEADERWRITER_HPP
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <span>
#include <concepts>
#include <fstream>
#include <filesystem>
template <typename T>
concept NumericType = std::integral<T> || std::floating_point<T>;
class HeaderWriter
{
std::ofstream stream;
std::string mName;
void WriteGuardStart();
void WriteGuardEnd();
public:
~HeaderWriter();
[[nodiscard]] bool Open(const std::filesystem::path& path, const std::string_view name);
void WriteDefine(const std::string_view name, const std::string_view value);
void WriteSymbol(const std::string_view name, const std::string_view type, std::size_t count);
template <NumericType T>
void WriteDefine(const std::string_view name, T value)
{
WriteDefine(name, std::to_string(value));
}
void WriteSize(int width, int height);
void WriteCharacterMap(const std::span<uint16_t> charData);
void WriteCollision(const std::span<uint8_t> collisionData);
void WriteObjects(const std::span<uint32_t> objData);
};
#endif//HEADERWRITER_HPP

163
src/swriter.cpp Normal file
View File

@@ -0,0 +1,163 @@
/* swwriter.cpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
#include "swriter.hpp"
#include <type_traits>
#include <limits>
#define GNU_STYLE 0
#define MASM_STYLE 1
#define HEX_STYLE GNU_STYLE
static inline constexpr char HexU(uint8_t h) { return "0123456789ABCDEF"[h >> 4]; }
static inline constexpr char HexL(uint8_t l) { return "0123456789ABCDEF"[l & 15]; }
#if HEX_STYLE == GNU_STYLE
template <typename T> static void CHex(std::ostream& s, T x);
template <> void CHex(std::ostream& s, uint8_t x)
{
if (x > 9) s << "0x";
if (x > 15) s << HexU(x);
s << HexL(x);
}
template <> void CHex(std::ostream& s, uint16_t x)
{
if (x > 9) s << "0x";
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8));
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8));
if (x > 15) s << HexU(static_cast<uint8_t>(x));
s << HexL(static_cast<uint8_t>(x));
}
template <> void CHex(std::ostream& s, uint32_t x)
{
if (x > 9) s << "0x";
if (x > 0xFFFFFFF) s << HexU(static_cast<uint8_t>(x >> 24));
if (x > 0xFFFFFF) s << HexL(static_cast<uint8_t>(x >> 24));
if (x > 0xFFFFF) s << HexU(static_cast<uint8_t>(x >> 16));
if (x > 65535) s << HexL(static_cast<uint8_t>(x >> 16));
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8));
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8));
if (x > 15) s << HexU(static_cast<uint8_t>(x));
s << HexL(static_cast<uint8_t>(x));
}
#elif HEX_STYLE == MASM_STYLE
template <typename T> static void MHex(std::ostream& s, T x);
template <> void MHex(std::ostream& s, uint8_t x)
{
if (x > 159) s << "0";
if (x > 15) s << HexU(x); else if (x > 9) s << "0";
s << HexL(x);
if (x > 9) s << "h";
}
template <> void MHex(std::ostream& s, uint16_t x)
{
if (x > 40959) s << "0";
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8)); else if (x > 2559) s << "0";
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8)); else if (x > 159) s << "0";
if (x > 15) s << HexU(static_cast<uint8_t>(x)); else if (x > 9) s << "0";
s << HexL(static_cast<uint8_t>(x));
if (x > 9) s << "h";
}
template <> void MHex(std::ostream& s, uint32_t x)
{
if (x > 0x9FFFFFFF) s << "0";
if (x > 0xFFFFFFF) s << HexU(static_cast<uint8_t>(x >> 24)); else if (x > 0x9FFFFFF) s << "0";
if (x > 0xFFFFFF) s << HexL(static_cast<uint8_t>(x >> 24)); else if (x > 0x9FFFFF) s << "0";
if (x > 0xFFFFF) s << HexU(static_cast<uint8_t>(x >> 16)); else if (x > 655359) s << "0";
if (x > 65535) s << HexL(static_cast<uint8_t>(x >> 16)); else if (x > 40959) s << "0";
if (x > 4095) s << HexU(static_cast<uint8_t>(x >> 8)); else if (x > 2559) s << "0";
if (x > 255) s << HexL(static_cast<uint8_t>(x >> 8)); else if (x > 159) s << "0";
if (x > 15) s << HexU(static_cast<uint8_t>(x)); else if (x > 9) s << "0";
s << HexL(static_cast<uint8_t>(x));
if (x > 9) s << "h";
}
#else
# error "Unknown hex style"
#endif
template <typename T> static constexpr const std::string_view DataType();
template <> constexpr const std::string_view DataType<uint8_t>() { return ".byte"; }
template <> constexpr const std::string_view DataType<uint16_t>() { return ".hword"; }
template <> constexpr const std::string_view DataType<uint32_t>() { return ".word"; }
template <typename I>
static void WriteArrayDetail(std::ostream& s, const I beg, const I end, int perCol)
{
typedef typename std::iterator_traits<I>::value_type Element;
int col = 0;
for (auto it = beg;;)
{
if (col == 0)
s << "\t" << DataType<Element>() << " ";
const Element e = *it;
#if HEX_STYLE == MASM_STYLE
MHex(s, e);
#elif HEX_STYLE == GNU_STYLE
CHex(s, e);
#endif
if (++it == end)
break;
if (++col < perCol)
{
s << ",";
}
else
{
s << std::endl;
col = 0;
}
}
s << std::endl;
}
void SWriter::WriteSymbol(const std::string_view suffix)
{
if (writes++ != 0)
stream << std::endl;
stream << "\t.section .rodata" << std::endl;
stream << "\t.align 2" << std::endl;
stream << "\t.global " << mName << suffix << std::endl;
stream << "\t.hidden " << mName << suffix << std::endl;
stream << mName << suffix << ":" << std::endl;
}
void SWriter::WriteArray(const std::string_view suffix, std::span<uint8_t> data, int numCols)
{
WriteSymbol(suffix);
WriteArrayDetail(stream, data.begin(), data.end(), numCols);
}
void SWriter::WriteArray(const std::string_view suffix, std::span<uint16_t> data, int numCols)
{
WriteSymbol(suffix);
WriteArrayDetail(stream, data.begin(), data.end(), numCols);
}
void SWriter::WriteArray(const std::string_view suffix, std::span<uint32_t> data, int numCols)
{
WriteSymbol(suffix);
WriteArrayDetail(stream, data.begin(), data.end(), numCols);
}
SWriter::~SWriter()
{
if (stream.is_open())
{
stream.close();
}
}
bool SWriter::Open(const std::filesystem::path& path, const std::string_view name)
{
mName = name;
stream.open(path);
return stream.is_open();
}

32
src/swriter.hpp Normal file
View File

@@ -0,0 +1,32 @@
/* swwriter.hpp - Copyright (C) 2024 a dinosaur (zlib, see COPYING.txt) */
#ifndef SWRITER_HPP
#define SWRITER_HPP
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <span>
#include <fstream>
#include <filesystem>
class SWriter
{
std::ofstream stream;
std::string mName;
int writes = 0;
void WriteSymbol(const std::string_view suffix);
public:
~SWriter();
bool Open(const std::filesystem::path& path, const std::string_view name);
void WriteArray(const std::string_view suffix, std::span<uint8_t> data, int numCols = 16);
void WriteArray(const std::string_view suffix, std::span<uint16_t> data, int numCols = 16);
void WriteArray(const std::string_view suffix, std::span<uint32_t> data, int numCols = 16);
};
#endif//SWRITER_HPP

View File

@@ -1,117 +1,116 @@
/* tmx2gba.cpp /* tmx2gba.cpp - Copyright (C) 2015-2024 a dinosaur (zlib, see COPYING.txt) */
Copyright (C) 2015-2022 a dinosaur
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "argparse.hpp"
#include "tmxreader.hpp" #include "tmxreader.hpp"
#include "tmxlayer.hpp" #include "tmxlayer.hpp"
#include "tmxobject.hpp" #include "tmxobject.hpp"
#include "headerwriter.hpp"
#include "swriter.hpp"
#include <iostream> #include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <map> #include <map>
#include <string>
#include <cstdint>
#include <algorithm> #include <algorithm>
#include <ultragetopt.h>
const std::string helpUsage = "Usage: tmx2gba [-h] [-f file] [-r offset] [-lyc name] [-p 0-15] [-m name;id] <-i inpath> <-o outpath>"; static const char* versionStr = "tmx2gba version 0.3, (c) 2015-2022 a dinosaur";
const std::string helpShort = "Run 'tmx2gba -h' to view all available options.";
const std::string helpFull = R"(
-h ------------ Display this help & command info.
-l <name> ----- Name of layer to use (default first layer in TMX).
-y <name> ----- Layer for palette mappings.
-c <name> ----- Output a separate 8bit collision map of the specified layer.
-r <offset> --- Offset tile indices (default 0).
-p <0-15> ----- Select which palette to use for 4-bit tilesets.
-m <name;id> -- Map an object name to an ID, will enable object exports.
-i <path> ----- Path to input TMX file.
-o <path> ----- Path to output files.
-f <file> ----- Specify a file to use for flags, will override any options specified on the command line.)";
struct Arguments struct Arguments
{ {
bool help = false; bool help = false, showVersion = false;
std::string inPath, outPath; std::string inPath, outPath;
std::string layer, collisionlay, paletteLay; std::string layer, collisionlay, paletteLay;
std::string flagFile; std::string flagFile;
int offset = 0; int offset = 0;
int palette = 0; int palette = 0;
std::vector<std::string> objMappings; std::vector<std::string> objMappings;
bool objExport = false;
}; };
void ParseArgs(int argc, char** argv, Arguments& p) using ArgParse::Option;
static const ArgParse::Options options =
{ {
int opt; Option::Optional('h', nullptr, "Display this help & command info"),
optreset = 1; Option::Optional('v', nullptr, "Display version & quit"),
while ((opt = getopt(argc, argv, "hr:l:c:p:y:m:i:o:f:")) > 0) Option::Optional('l', "name", "Name of layer to use (default first layer in TMX)"),
Option::Optional('y', "name", "Layer for palette mappings"),
Option::Optional('c', "name", "Output a separate 8bit collision map of the specified layer"),
Option::Optional('r', "offset", "Offset tile indices (default 0)"),
Option::Optional('p', "0-15", "Select which palette to use for 4-bit tilesets"),
Option::Optional('m', "name;id", "Map an object name to an ID, will enable object exports"),
Option::Required('i', "inpath", "Path to input TMX file"),
Option::Required('o', "outpath", "Path to output files"),
Option::Optional('f', "file", "Specify a file to use for flags, will override any options"
" specified on the command line")
};
bool ParseArgs(int argc, char** argv, Arguments& params)
{
auto parser = ArgParse::ArgParser(argv[0], options, [&](int opt, const std::string_view arg)
-> ArgParse::ParseCtrl
{ {
switch (opt) using ArgParse::ParseCtrl;
try
{ {
case ('h'): switch (opt)
p.help = true; {
return; case 'h': params.help = true; return ParseCtrl::QUIT_EARLY;
case 'v': params.showVersion = true; return ParseCtrl::QUIT_EARLY;
case 'l': params.layer = arg; return ParseCtrl::CONTINUE;
case 'c': params.collisionlay = arg; return ParseCtrl::CONTINUE;
case 'y': params.paletteLay = arg; return ParseCtrl::CONTINUE;
case 'r': params.offset = std::stoi(std::string(arg)); return ParseCtrl::CONTINUE;
case 'p': params.palette = std::stoi(std::string(arg)); return ParseCtrl::CONTINUE;
case 'm': params.objMappings.emplace_back(arg); return ParseCtrl::CONTINUE;
case 'i': params.inPath = arg; return ParseCtrl::CONTINUE;
case 'o': params.outPath = arg; return ParseCtrl::CONTINUE;
case 'f': params.flagFile = arg; return ParseCtrl::CONTINUE;
case ('l'): p.layer = optarg; break; default: return ParseCtrl::QUIT_ERR_UNKNOWN;
case ('c'): p.collisionlay = optarg; break; }
case ('y'): p.paletteLay = optarg; break;
case ('r'): p.offset = std::stoi(optarg); break;
case ('p'): p.palette = std::stoi(optarg); break;
case ('m'):
p.objExport = true;
p.objMappings.emplace_back(optarg);
break;
case ('i'): p.inPath = optarg; break;
case ('o'): p.outPath = optarg; break;
case ('f'): p.flagFile = optarg; break;
default:
break;
} }
} catch (std::invalid_argument const&) { return ParseCtrl::QUIT_ERR_INVALID; }
} catch (std::out_of_range const&) { return ParseCtrl::QUIT_ERR_RANGE; }
});
bool CheckArgs(const Arguments& params) if (!parser.Parse(std::span(argv + 1, argc - 1)))
{ return false;
// Check my paranoia.
if (params.help || params.showVersion)
return true;
if (!params.flagFile.empty())
{
std::ifstream paramFile(params.flagFile);
if (!paramFile.is_open())
{
std::cerr << "Failed to open param file." << std::endl;
return false;
}
std::vector<std::string> tokens;
if (!ArgParse::ReadParamFile(tokens, paramFile))
{
std::cerr << "Failed to read param file: Unterminated quote string." << std::endl;
return false;
}
if (!parser.Parse(tokens))
return false;
}
// Check my paranoia
if (params.inPath.empty()) if (params.inPath.empty())
{ {
std::cerr << "No input file specified." << std::endl; parser.DisplayError("No input file specified.");
std::cout << helpUsage << std::endl << helpShort << std::endl;
return false; return false;
} }
if (params.outPath.empty()) if (params.outPath.empty())
{ {
std::cerr << "No output file specified." << std::endl; parser.DisplayError("No output file specified.");
std::cout << helpUsage << std::endl << helpShort << std::endl;
return false; return false;
} }
if (params.palette < 0 || params.palette > 15) if (params.palette < 0 || params.palette > 15)
{ {
std::cerr << "Invalid palette index." << std::endl; parser.DisplayError("Invalid palette index.");
std::cout << helpUsage << std::endl << helpShort << std::endl;
return false; return false;
} }
@@ -119,125 +118,25 @@ bool CheckArgs(const Arguments& params)
} }
template <typename T> constexpr const char* DatType();
template <> constexpr const char* DatType<uint8_t>() { return ".byte"; }
template <> constexpr const char* DatType<uint16_t>() { return ".hword"; }
template <> constexpr const char* DatType<uint32_t>() { return ".word"; }
template <typename T>
void WriteArray(std::ofstream& aOut, const std::vector<T>& aDat, int aPerCol = 16)
{
int col = 0;
aOut.setf(std::ios::hex, std::ios::basefield);
aOut.setf(std::ios::showbase);
size_t i = 0;
for (T element : aDat)
{
if (col == 0)
aOut << "\t" << DatType<T>() << " ";
aOut << std::hex << (int)element;
if (i < aDat.size() - 1)
{
if (++col < aPerCol)
{
aOut << ",";
}
else
{
aOut << "" << std::endl;
col = 0;
}
}
++i;
}
}
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
Arguments p; Arguments p;
ParseArgs(argc, argv, p); if (!ParseArgs(argc, argv, p))
return 1;
if (p.help) if (p.help)
{ {
std::cout << helpUsage << std::endl << helpFull << std::endl; options.ShowHelpUsage(argv[0], std::cout);
return 0;
}
if (p.showVersion)
{
std::cout << versionStr << std::endl;
return 0; return 0;
} }
if (!p.flagFile.empty()) // Object mappings
{
std::ifstream paramFile(p.flagFile);
if (!paramFile.is_open())
{
std::cerr << "Failed to open param file." << std::endl;
return -1;
}
std::vector<std::string> fileArgTokens;
fileArgTokens.push_back("auu~~");
bool carry = false;
std::string rawToken;
while (!paramFile.eof())
{
if (carry)
{
std::string tmp;
paramFile >> tmp;
rawToken += " ";
rawToken += tmp;
}
else
{
rawToken.clear();
paramFile >> rawToken;
}
if (rawToken.empty())
continue;
bool qFr = rawToken[0] == '"';
bool qBk = rawToken[rawToken.length() - 1] == '"';
if (qFr && qBk)
{
fileArgTokens.push_back(rawToken.substr(1, rawToken.length() - 2));
}
else
if (qFr)
{
fileArgTokens.push_back(rawToken.substr(1, rawToken.length() - 1));
carry = true;
}
else
if (qBk)
{
fileArgTokens.push_back(rawToken.substr(0, rawToken.length() - 1));
carry = false;
}
else
{
fileArgTokens.push_back(rawToken);
}
}
std::vector<const char*> fileArgs;
fileArgs.reserve(fileArgTokens.size());
for (const auto& token : fileArgTokens)
fileArgs.push_back(token.c_str());
fileArgs.push_back(nullptr);
ParseArgs(static_cast<int>(fileArgs.size()) - 1, (char**)fileArgs.data(), p);
}
if (!CheckArgs(p))
return -1;
// Object mappings.
std::map<std::string, uint32_t> objMapping; std::map<std::string, uint32_t> objMapping;
if (p.objExport) if (!p.objMappings.empty())
{ {
for (const auto& objToken : p.objMappings) for (const auto& objToken : p.objMappings)
{ {
@@ -245,7 +144,7 @@ int main(int argc, char** argv)
if (splitter == std::string::npos) if (splitter == std::string::npos)
{ {
std::cerr << "Malformed mapping (missing a splitter)." << std::endl; std::cerr << "Malformed mapping (missing a splitter)." << std::endl;
return -1; return 1;
} }
try try
@@ -262,21 +161,21 @@ int main(int argc, char** argv)
} }
} }
// Open & read input file. // Open & read input file
TmxReader tmx; TmxReader tmx;
std::ifstream fin(p.inPath); std::ifstream fin(p.inPath);
if (!fin.is_open()) if (!fin.is_open())
{ {
std::cerr << "Failed to open input file." << std::endl; std::cerr << "Failed to open input file." << std::endl;
return -1; return 1;
} }
tmx.Open(fin); tmx.Open(fin);
// Get layers. // Get layers
if (tmx.GetLayerCount() == 0) if (tmx.GetLayerCount() == 0)
{ {
std::cerr << "No layers found." << std::endl; std::cerr << "No layers found." << std::endl;
return -1; return 1;
} }
const TmxLayer* layerGfx = p.layer.empty() const TmxLayer* layerGfx = p.layer.empty()
? tmx.GetLayer(0) ? tmx.GetLayer(0)
@@ -291,52 +190,49 @@ int main(int argc, char** argv)
if (layerGfx == nullptr) if (layerGfx == nullptr)
{ {
std::cerr << "Input layer not found." << std::endl; std::cerr << "Input layer not found." << std::endl;
return -1; return 1;
} }
// Open output files. // Get name from file
std::ofstream foutS(p.outPath + ".s"); //TODO: properly sanitise
std::ofstream foutH(p.outPath + ".h"); int slashPos = std::max(
if (!foutS.is_open() || !foutH.is_open()) static_cast<int>(p.outPath.find_last_of('/')),
{ static_cast<int>(p.outPath.find_last_of('\\')));
std::cerr << "Failed to create output file(s).";
return -1;
}
int slashPos = std::max((int)p.outPath.find_last_of('/'), (int)p.outPath.find_last_of('\\'));
std::string name = p.outPath; std::string name = p.outPath;
if (slashPos != -1) if (slashPos != -1)
name = name.substr(slashPos + 1); name = name.substr(slashPos + 1);
// Write header guards. // Open output files
std::string guard = "TMX2GBA_" + name; SWriter outS; HeaderWriter outH;
for (auto& c: guard) if (!outS.Open(p.outPath + ".s", name))
c = static_cast<char>(toupper(c)); {
foutH << "#ifndef " << guard << std::endl; std::cerr << "Failed to create output file \"" << p.outPath << ".s\".";
foutH << "#define " << guard << std::endl; return 1;
foutH << std::endl; }
foutH << "#define " << name << "Width " << tmx.GetWidth() << std::endl; if (!outH.Open(p.outPath + ".h", name))
foutH << "#define " << name << "Height " << tmx.GetHeight() << std::endl; {
foutH << std::endl; std::cerr << "Failed to create output file \"" << p.outPath << ".h\".";
return 1;
}
// Convert to GBA-friendly charmap data. // Convert to GBA-friendly charmap data
const uint32_t* gfxTiles = layerGfx->GetData(); const uint32_t* gfxTiles = layerGfx->GetData();
const uint32_t* palTiles = (layerPal == nullptr) ? nullptr : layerPal->GetData(); const uint32_t* palTiles = (layerPal == nullptr) ? nullptr : layerPal->GetData();
std::vector<uint16_t> charDat; std::vector<uint16_t> charDat;
size_t numTiles = static_cast<size_t>(layerGfx->GetWidth()) * static_cast<size_t>(layerGfx->GetHeight()); const size_t numTiles = static_cast<size_t>(layerGfx->GetWidth()) * static_cast<size_t>(layerGfx->GetHeight());
charDat.reserve(numTiles); charDat.reserve(numTiles);
for (size_t i = 0; i < numTiles; ++i) for (size_t i = 0; i < numTiles; ++i)
{ {
uint32_t read = (*gfxTiles++); uint32_t read = (*gfxTiles++);
uint16_t tile = (uint16_t)std::max<int32_t>(0, tmx.LidFromGid(read & ~TmxLayer::FLIP_MASK) + p.offset); uint16_t tile = std::max(0, static_cast<int>(tmx.LidFromGid(read & ~TmxLayer::FLIP_MASK)) + p.offset);
uint8_t flags = 0x0; uint8_t flags = 0x0;
// Get flipped! // Get flipped!
flags |= (read & TmxLayer::FLIP_HORZ) ? 0x4 : 0x0; flags |= (read & TmxLayer::FLIP_HORZ) ? 0x4 : 0x0;
flags |= (read & TmxLayer::FLIP_VERT) ? 0x8 : 0x0; flags |= (read & TmxLayer::FLIP_VERT) ? 0x8 : 0x0;
// Determine palette ID. // Determine palette ID
uint32_t idx = 0; uint32_t idx = 0;
if (palTiles != nullptr) if (palTiles != nullptr)
idx = tmx.LidFromGid((*palTiles++) & ~TmxLayer::FLIP_MASK); idx = tmx.LidFromGid((*palTiles++) & ~TmxLayer::FLIP_MASK);
@@ -347,33 +243,25 @@ int main(int argc, char** argv)
charDat.push_back(tile | (static_cast<uint16_t>(flags) << 8)); charDat.push_back(tile | (static_cast<uint16_t>(flags) << 8));
} }
// Save out charmap. // Write out charmap
foutH << "#define " << name << "TilesLen " << charDat.size() * 2 << std::endl; outH.WriteSize(tmx.GetWidth(), tmx.GetHeight());
foutH << "extern const unsigned short " << name << "Tiles[" << charDat.size() << "];" << std::endl; outH.WriteCharacterMap(charDat);
foutH << std::endl; outS.WriteArray("Tiles", charDat);
foutS << "\t.section .rodata" << std::endl; // Convert collision map & write it out
foutS << "\t.align 2" << std::endl;
foutS << "\t.global " << name << "Tiles" << std::endl;
foutS << "\t.hidden " << name << "Tiles" << std::endl;
foutS << name << "Tiles" << ":" << std::endl;
WriteArray<uint16_t>(foutS, charDat);
foutS << std::endl;
// Convert collision map & save it out.
if (layerCls != nullptr) if (layerCls != nullptr)
{ {
std::vector<uint8_t> vucCollisionDat; std::vector<uint8_t> collisionDat;
vucCollisionDat.reserve(layerCls->GetWidth() * layerCls->GetHeight()); collisionDat.reserve(layerCls->GetWidth() * layerCls->GetHeight());
gfxTiles = layerCls->GetData(); gfxTiles = layerCls->GetData();
for (int i = 0; i < layerCls->GetWidth() * layerCls->GetHeight(); ++i) for (int i = 0; i < layerCls->GetWidth() * layerCls->GetHeight(); ++i)
{ {
uint8_t ucTile = (uint8_t)tmx.LidFromGid((*gfxTiles++) & ~TmxLayer::FLIP_MASK); uint8_t ucTile = static_cast<uint8_t>(tmx.LidFromGid((*gfxTiles++) & ~TmxLayer::FLIP_MASK));
vucCollisionDat.push_back(ucTile); collisionDat.push_back(ucTile);
} }
// Try to nicely append "_collision" to the output name. // Try to nicely append "_collision" to the output name
std::string path; std::string path;
size_t extPos = p.outPath.find_last_of('.'); size_t extPos = p.outPath.find_last_of('.');
if (extPos != std::string::npos) if (extPos != std::string::npos)
@@ -381,22 +269,12 @@ int main(int argc, char** argv)
else else
path = p.outPath + "_collision"; path = p.outPath + "_collision";
// Save it out. // Write collision
foutH << "#define " << name << "CollisionLen " << vucCollisionDat.size() << std::endl; outH.WriteCollision(collisionDat);
foutH << "extern const unsigned char " << name << "Collision[" << vucCollisionDat.size() << "];" << std::endl; outS.WriteArray("Collision", collisionDat, 32);
foutH << std::endl;
foutS << std::endl;
foutS << "\t.section .rodata" << std::endl;
foutS << "\t.align 2" << std::endl;
foutS << "\t.global " << name << "Collision" << std::endl;
foutS << "\t.hidden " << name << "Collision" << std::endl;
foutS << name << "Collision" << ":" << std::endl;
WriteArray<uint8_t>(foutS, vucCollisionDat);
foutS << std::endl;
} }
if (p.objExport) if (!p.objMappings.empty())
{ {
std::vector<uint32_t> objDat; std::vector<uint32_t> objDat;
for (size_t i = 0; i < tmx.GetObjectCount(); ++i) for (size_t i = 0; i < tmx.GetObjectCount(); ++i)
@@ -409,30 +287,14 @@ int main(int argc, char** argv)
float x, y; float x, y;
obj->GetPos(x, y); obj->GetPos(x, y);
objDat.push_back(it->second); objDat.push_back(it->second);
objDat.push_back((int)(x * 256.0f)); objDat.push_back(static_cast<int>(x * 256.0f));
objDat.push_back((int)(y * 256.0f)); objDat.push_back(static_cast<int>(y * 256.0f));
} }
// Save it out. // Write objects
foutH << "#define " << name << "ObjCount " << objDat.size() / 3 << std::endl; outH.WriteObjects(objDat);
foutH << "#define " << name << "ObjdatLen " << objDat.size() * sizeof(int) << std::endl; outS.WriteArray("Objdat", objDat);
foutH << "extern const unsigned int " << name << "Objdat[" << objDat.size() << "];" << std::endl;
foutH << std::endl;
foutS << std::endl;
foutS << "\t.section .rodata" << std::endl;
foutS << "\t.align 2" << std::endl;
foutS << "\t.global " << name << "Objdat" << std::endl;
foutS << "\t.hidden " << name << "Objdat" << std::endl;
foutS << name << "Objdat" << ":" << std::endl;
WriteArray<uint32_t>(foutS, objDat);
foutS << std::endl;
} }
foutH << "#endif//" << guard << std::endl;
foutH.close();
foutS.close();
return 0; return 0;
} }

View File

@@ -1,27 +1,7 @@
/* tmxlayer.cpp /* tmxlayer.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
Copyright (C) 2015-2022 a dinosaur #ifndef TMXLAYER_HPP
#define TMXLAYER_HPP
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef TMXLAYER_H
#define TMXLAYER_H
#include <string> #include <string>
#include <cstdint> #include <cstdint>
@@ -51,4 +31,4 @@ private:
uint32_t* mTileDat; uint32_t* mTileDat;
}; };
#endif//TMXLAYER_H #endif//TMXLAYER_HPP

View File

@@ -1,27 +1,7 @@
/* tmxobject.cpp /* tmxobject.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
Copyright (C) 2015-2022 a dinosaur #ifndef TMXOBJECT_HPP
#define TMXOBJECT_HPP
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef TMXOBJECT_H
#define TMXOBJECT_H
#include <string> #include <string>
#include <utility> #include <utility>
@@ -42,4 +22,4 @@ private:
float mX, mY; float mX, mY;
}; };
#endif//TMXOBJECT_H #endif//TMXOBJECT_HPP

View File

@@ -1,24 +1,4 @@
/* tmxreader.cpp /* tmxreader.cpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
Copyright (C) 2015-2022 a dinosaur
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "tmxreader.hpp" #include "tmxreader.hpp"
#include "tmxtileset.hpp" #include "tmxtileset.hpp"
@@ -34,12 +14,12 @@
TmxReader::~TmxReader() TmxReader::~TmxReader()
{ {
// Delete old tilesets. // Delete old tilesets
for (auto pTileset : mTilesets) for (auto pTileset : mTilesets)
delete pTileset; delete pTileset;
mTilesets.clear(); mTilesets.clear();
// Delete old layers. // Delete old layers
for (auto pLay : mLayers) for (auto pLay : mLayers)
delete pLay; delete pLay;
mLayers.clear(); mLayers.clear();
@@ -57,13 +37,13 @@ bool TmxReader::DecodeMap(uint32_t* aOut, size_t aOutSize, const std::string& aB
std::size_t endOff = std::distance(end, aBase64Dat.rend()) - begOff; std::size_t endOff = std::distance(end, aBase64Dat.rend()) - begOff;
auto trimmed = aBase64Dat.substr(begOff, endOff); auto trimmed = aBase64Dat.substr(begOff, endOff);
// Decode base64 string. // Decode base64 string
std::string decoded = base64_decode(trimmed); std::string decoded = base64_decode(trimmed);
// Decompress compressed data. // Decompress compressed data
auto dstSize = static_cast<mz_ulong>(aOutSize); auto dstSize = static_cast<mz_ulong>(aOutSize);
int res = uncompress( int res = uncompress(
(unsigned char*)aOut, reinterpret_cast<unsigned char*>(aOut),
&dstSize, &dstSize,
reinterpret_cast<const unsigned char*>(decoded.data()), reinterpret_cast<const unsigned char*>(decoded.data()),
static_cast<mz_ulong>(decoded.size())); static_cast<mz_ulong>(decoded.size()));
@@ -82,20 +62,20 @@ void TmxReader::ReadTileset(rapidxml::xml_node<>* aXNode)
const char* source = ""; const char* source = "";
uint32_t firstGid = 0; uint32_t firstGid = 0;
// Read name. // Read name
xAttrib = aXNode->first_attribute("name"); xAttrib = aXNode->first_attribute("name");
if (xAttrib != nullptr) if (xAttrib != nullptr)
name = xAttrib->value(); name = xAttrib->value();
// Read source. // Read source
xAttrib = aXNode->first_attribute("source"); xAttrib = aXNode->first_attribute("source");
if (xAttrib != nullptr) if (xAttrib != nullptr)
source = xAttrib->value(); source = xAttrib->value();
// Read first global ID. // Read first global ID
xAttrib = aXNode->first_attribute("firstgid"); xAttrib = aXNode->first_attribute("firstgid");
if (xAttrib != nullptr) if (xAttrib != nullptr)
firstGid = std::stoul(xAttrib->value()); firstGid = static_cast<uint32_t>(std::stoul(xAttrib->value()));
mTilesets.push_back(new TmxTileset(name, source, firstGid)); mTilesets.push_back(new TmxTileset(name, source, firstGid));
} }
@@ -108,26 +88,26 @@ void TmxReader::ReadLayer(rapidxml::xml_node<>* aXNode)
int height = 0; int height = 0;
uint32_t* tileDat = nullptr; uint32_t* tileDat = nullptr;
// Read name. // Read name
xAttrib = aXNode->first_attribute("name"); xAttrib = aXNode->first_attribute("name");
if (xAttrib != nullptr) if (xAttrib != nullptr)
name = xAttrib->value(); name = xAttrib->value();
// Read width. // Read width
xAttrib = aXNode->first_attribute("width"); xAttrib = aXNode->first_attribute("width");
if (xAttrib != nullptr) if (xAttrib != nullptr)
width = std::stoi(xAttrib->value()); width = std::stoi(xAttrib->value());
// Read height. // Read height
xAttrib = aXNode->first_attribute("height"); xAttrib = aXNode->first_attribute("height");
if (xAttrib != nullptr) if (xAttrib != nullptr)
height = std::stoi(xAttrib->value()); height = std::stoi(xAttrib->value());
// Read tile data. // Read tile data
auto xData = aXNode->first_node("data"); auto xData = aXNode->first_node("data");
if (xData != nullptr) if (xData != nullptr)
{ {
// TODO: don't assume base64 & zlib. // TODO: don't assume base64 & zlib
tileDat = new uint32_t[width * height]; tileDat = new uint32_t[width * height];
if (!DecodeMap(tileDat, width * height * sizeof(uint32_t), std::string(xData->value()))) if (!DecodeMap(tileDat, width * height * sizeof(uint32_t), std::string(xData->value())))
{ {
@@ -151,17 +131,17 @@ void TmxReader::ReadObjects(rapidxml::xml_node<>* aXNode)
float x = 0.0f; float x = 0.0f;
float y = 0.0f; float y = 0.0f;
// Read name. // Read name
xAttrib = xNode->first_attribute("name"); xAttrib = xNode->first_attribute("name");
if (xAttrib != nullptr) if (xAttrib != nullptr)
name = xAttrib->value(); name = xAttrib->value();
// Read X pos. // Read X pos
xAttrib = xNode->first_attribute("x"); xAttrib = xNode->first_attribute("x");
if (xAttrib != nullptr) if (xAttrib != nullptr)
x = std::stof(xAttrib->value()); x = std::stof(xAttrib->value());
// Read Y pos. // Read Y pos
xAttrib = xNode->first_attribute("y"); xAttrib = xNode->first_attribute("y");
if (xAttrib != nullptr) if (xAttrib != nullptr)
y = std::stof(xAttrib->value()); y = std::stof(xAttrib->value());
@@ -172,44 +152,44 @@ void TmxReader::ReadObjects(rapidxml::xml_node<>* aXNode)
void TmxReader::Open(std::istream& aIn) void TmxReader::Open(std::istream& aIn)
{ {
// Delete old tilesets. // Delete old tilesets
for (auto tileset : mTilesets) for (auto tileset : mTilesets)
delete tileset; delete tileset;
mTilesets.clear(); mTilesets.clear();
// Delete old layers. // Delete old layers
for (auto layer : mLayers) for (auto layer : mLayers)
delete layer; delete layer;
mLayers.clear(); mLayers.clear();
mGidTable.clear(); mGidTable.clear();
// Read string into a buffer. // Read string into a buffer
std::stringstream buf; std::stringstream buf;
buf << aIn.rdbuf(); buf << aIn.rdbuf();
std::string strXml = buf.str(); std::string strXml = buf.str();
buf.clear(); buf.clear();
// Parse document. // Parse document
rapidxml::xml_document<> xDoc; rapidxml::xml_document<> xDoc;
xDoc.parse<0>((char*)strXml.c_str()); xDoc.parse<0>(const_cast<char*>(strXml.c_str()));
// Get map node. // Get map node
auto xMap = xDoc.first_node("map"); auto xMap = xDoc.first_node("map");
if (xMap == nullptr) if (xMap == nullptr)
return; return;
// Read map attribs. // Read map attribs
rapidxml::xml_attribute<>* xAttrib = nullptr; rapidxml::xml_attribute<>* xAttrib = nullptr;
if ((xAttrib = xMap->first_attribute("width")) != nullptr) if ((xAttrib = xMap->first_attribute("width")) != nullptr)
mWidth = std::stoi(xAttrib->value()); mWidth = std::stoi(xAttrib->value());
if ((xAttrib = xMap->first_attribute("height")) != nullptr) if ((xAttrib = xMap->first_attribute("height")) != nullptr)
mHeight = std::stoi(xAttrib->value()); mHeight = std::stoi(xAttrib->value());
// Read nodes. // Read nodes
for (auto xNode = xMap->first_node(); xNode != nullptr; xNode = xNode->next_sibling()) for (auto xNode = xMap->first_node(); xNode != nullptr; xNode = xNode->next_sibling())
{ {
// Read layer nodes. // Read layer nodes
if (strcmp(xNode->name(), "layer") == 0) if (strcmp(xNode->name(), "layer") == 0)
ReadLayer(xNode); ReadLayer(xNode);
else else
@@ -220,7 +200,7 @@ void TmxReader::Open(std::istream& aIn)
ReadObjects(xNode); ReadObjects(xNode);
} }
// Generate global id table. // Generate global id table
for (auto tileset : mTilesets) for (auto tileset : mTilesets)
mGidTable.push_back(tileset->GetFirstGid()); mGidTable.push_back(tileset->GetFirstGid());
std::sort(mGidTable.rbegin(), mGidTable.rend()); std::sort(mGidTable.rbegin(), mGidTable.rend());

View File

@@ -1,5 +1,7 @@
#ifndef TMXREADER_H /* tmxreader.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
#define TMXREADER_H
#ifndef TMXREADER_HPP
#define TMXREADER_HPP
#include <istream> #include <istream>
#include <vector> #include <vector>
@@ -45,4 +47,4 @@ private:
}; };
#endif//TMXREADER_H #endif//TMXREADER_HPP

View File

@@ -1,27 +1,7 @@
/* tmxtileset.cpp /* tmxtileset.hpp - Copyright (C) 2015-2022 a dinosaur (zlib, see COPYING.txt) */
Copyright (C) 2015-2022 a dinosaur #ifndef TMXTILESET_HPP
#define TMXTILESET_HPP
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef TMXTILESET_H
#define TMXTILESET_H
#include <string> #include <string>
#include <cstdint> #include <cstdint>
@@ -43,7 +23,6 @@ private:
std::string mName; std::string mName;
std::string mSource; std::string mSource;
uint32_t mFirstGid; uint32_t mFirstGid;
}; };
#endif//TMXTILESET_H #endif//TMXTILESET_HPP