├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── merge.yml │ └── release.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── compile_flags.txt ├── include ├── .clang-format ├── .clang-tidy └── ezOptionParser │ ├── MIT-LICENSE │ ├── README.md │ └── ezOptionParser.hpp ├── src ├── sfcRom.cpp ├── sfcRom.hpp └── superfamicheck.cpp └── test ├── CMakeLists.txt ├── Makefile ├── data └── public │ ├── rom0.sfc │ └── rom1.sfc └── test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | ColumnLimit: 120 4 | --- 5 | Language: Cpp 6 | IndentWidth: 4 7 | ConstructorInitializerIndentWidth: 4 8 | ContinuationIndentWidth: 4 9 | ColumnLimit: 130 10 | MaxEmptyLinesToKeep: 2 11 | AlignAfterOpenBracket: BlockIndent 12 | AlignArrayOfStructures: Right 13 | AlignEscapedNewlines: DontAlign 14 | AlignOperands: AlignAfterOperator 15 | AlignTrailingComments: 16 | Kind: Always 17 | OverEmptyLines: 1 18 | AllowShortBlocksOnASingleLine: Always 19 | AllowShortFunctionsOnASingleLine: InlineOnly 20 | AllowShortIfStatementsOnASingleLine: WithoutElse 21 | AlwaysBreakBeforeMultilineStrings: true 22 | AlwaysBreakTemplateDeclarations: true 23 | BinPackArguments: true 24 | BinPackParameters: true 25 | BraceWrapping: 26 | AfterCaseLabel: false 27 | AfterClass: false 28 | AfterControlStatement: Never 29 | AfterEnum: false 30 | AfterExternBlock: false 31 | AfterFunction: false 32 | AfterNamespace: false 33 | AfterObjCDeclaration: false 34 | AfterStruct: false 35 | AfterUnion: false 36 | BeforeCatch: false 37 | BeforeElse: false 38 | BeforeLambdaBody: false 39 | BeforeWhile: false 40 | IndentBraces: false 41 | SplitEmptyFunction: false 42 | SplitEmptyRecord: false 43 | SplitEmptyNamespace: false 44 | BreakBeforeBraces: Attach 45 | BreakBeforeTernaryOperators: true 46 | BreakConstructorInitializers: BeforeColon 47 | BreakInheritanceList: BeforeColon 48 | BreakStringLiterals: true 49 | CompactNamespaces: true 50 | EmptyLineAfterAccessModifier: Never 51 | EmptyLineBeforeAccessModifier: LogicalBlock 52 | ExperimentalAutoDetectBinPacking: true 53 | DerivePointerAlignment: false 54 | FixNamespaceComments: true 55 | IncludeBlocks: Preserve 56 | IndentAccessModifiers: false 57 | IndentCaseBlocks: false 58 | IndentCaseLabels: false 59 | IndentExternBlock: AfterExternBlock 60 | IndentGotoLabels: true 61 | IndentPPDirectives: BeforeHash 62 | IndentWrappedFunctionNames: false 63 | InsertBraces: true 64 | InsertNewlineAtEOF: true 65 | InsertTrailingCommas: None 66 | KeepEmptyLinesAtEOF: false 67 | KeepEmptyLinesAtTheStartOfBlocks: true 68 | LambdaBodyIndentation: OuterScope 69 | LineEnding: LF 70 | NamespaceIndentation: None 71 | PackConstructorInitializers: Never 72 | PointerAlignment: Left 73 | PPIndentWidth: -1 74 | QualifierAlignment: Left 75 | ReferenceAlignment: Pointer 76 | ReflowComments: true 77 | RemoveBracesLLVM: false 78 | RemoveParentheses: Leave 79 | RemoveSemicolon: false 80 | SeparateDefinitionBlocks: Always 81 | ShortNamespaceLines: 5 82 | SortIncludes: CaseInsensitive 83 | SortUsingDeclarations: Lexicographic 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterLogicalNot: false 86 | SpaceAfterTemplateKeyword: false 87 | SpaceAroundPointerQualifiers: Default 88 | SpaceBeforeAssignmentOperators: true 89 | SpaceBeforeCaseColon: false 90 | SpaceBeforeCpp11BracedList: false 91 | SpaceBeforeCtorInitializerColon: true 92 | SpaceBeforeInheritanceColon: false 93 | SpaceBeforeParens: ControlStatementsExceptControlMacros 94 | SpaceBeforeRangeBasedForLoopColon: true 95 | SpaceBeforeSquareBrackets: false 96 | SpaceInEmptyBlock: false 97 | SpacesBeforeTrailingComments: 1 98 | SpacesInAngles: Never 99 | SpacesInLineCommentPrefix: 100 | Minimum: 1 101 | Maximum: -1 102 | SpacesInParens: Never 103 | SpacesInSquareBrackets: false 104 | TabWidth: 4 105 | UseTab: Never 106 | ... 107 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '-*, 2 | cppcoreguidelines-init-variables, 3 | cppcoreguidelines-prefer-member-initializer, 4 | misc-confusable-identifiers, 5 | misc-const-correctness, 6 | misc-definitions-in-headers, 7 | misc-header-include-cycle, 8 | misc-include-cleaner, 9 | misc-misleading-bidirectional, 10 | misc-misleading-identifier, 11 | misc-misplaced-const, 12 | misc-redundant-expression, 13 | misc-unused-parameters, 14 | misc-unused-using-decls, 15 | modernize-concat-nested-namespaces, 16 | modernize-deprecated-headers, 17 | modernize-loop-convert, 18 | modernize-pass-by-value, 19 | modernize-redundant-void-arg, 20 | modernize-shrink-to-fit, 21 | modernize-use-auto, 22 | modernize-use-bool-literals, 23 | modernize-use-emplace, 24 | modernize-use-nullptr, 25 | modernize-use-starts-ends-with, 26 | modernize-use-std-numbers, 27 | performance-faster-string-find, 28 | performance-for-range-copy, 29 | performance-implicit-conversion-in-loop, 30 | performance-inefficient-algorithm, 31 | performance-inefficient-string-concatenation, 32 | performance-inefficient-vector-operation, 33 | readability-string-compare, 34 | readability-braces-around-statements, 35 | readability-identifier-length, 36 | readability-identifier-naming, 37 | readability-string-compare, 38 | ' 39 | WarningsAsErrors: true 40 | FormatStyle: file 41 | CheckOptions: 42 | - key: readability-identifier-length.MinimumLoopCounterNameLength 43 | value: 2 44 | - key: readability-identifier-length.MinimumParameterNameLength 45 | value: 2 46 | - key: readability-identifier-length.MinimumVariableNameLength 47 | value: 2 48 | -------------------------------------------------------------------------------- /.github/workflows/merge.yml: -------------------------------------------------------------------------------- 1 | name: Merge 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: main 7 | 8 | jobs: 9 | unit_tests: 10 | runs-on: macos-14 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Run tests 15 | run: cd test && make 16 | build_ubuntu: 17 | needs: unit_tests 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Build 23 | run: make 24 | build_macos: 25 | needs: unit_tests 26 | runs-on: macos-14 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Build 31 | run: make 32 | build_windows: 33 | needs: unit_tests 34 | runs-on: windows-latest 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | - name: Build 39 | run: | 40 | cmake -Bbuild -DCMAKE_BUILD_TYPE=Release 41 | cmake --build build --config Release 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | release_macos: 10 | runs-on: macos-14 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Build 15 | run: | 16 | make 17 | zip -j superfamicheck_macos_${{ github.ref_name }}.zip build/release/superfamicheck README.md LICENSE 18 | - name: Release 19 | uses: softprops/action-gh-release@v2 20 | if: startsWith(github.ref, 'refs/tags/') 21 | with: 22 | files: superfamicheck_macos_${{ github.ref_name }}.zip 23 | release_win64: 24 | runs-on: windows-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | - name: Build 29 | run: | 30 | cmake -Bbuild -DCMAKE_BUILD_TYPE=Release 31 | cmake --build build --config Release 32 | move build\Release\superfamicheck.exe superfamicheck.exe 33 | Compress-Archive -LiteralPath superfamicheck.exe, README.md, LICENSE -DestinationPath superfamicheck_win64_${{ github.ref_name }}.zip 34 | - name: Release 35 | uses: softprops/action-gh-release@v2 36 | if: startsWith(github.ref, 'refs/tags/') 37 | with: 38 | files: superfamicheck_win64_${{ github.ref_name }}.zip 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | test/data/private 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "llvm-vs-code-extensions.vscode-clangd", 4 | "ms-vscode.cpptools", 5 | "ms-vscode.cpptools-extension-pack", 6 | "ms-vscode.cmake-tools" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "lldb", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/build/superfamicheck", 9 | "args": [ 10 | "test/data/public/rom1.sfc" 11 | ], 12 | "preLaunchTask": "${defaultBuildTask}", 13 | }, 14 | { 15 | "name": "Test", 16 | "type": "lldb", 17 | "request": "launch", 18 | "cwd": "${workspaceFolder}/test", 19 | "program": "${workspaceFolder}/test/build/test", 20 | "args": [], 21 | "preLaunchTask": "CMake: build test", 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.intelliSenseEngine": "disabled", 3 | "clangd.arguments": [ 4 | "--background-index", 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cmake", 6 | "label": "CMake: configure", 7 | "command": "configure", 8 | "targets": [ 9 | "all" 10 | ] 11 | }, 12 | { 13 | "type": "cmake", 14 | "label": "CMake: build", 15 | "command": "build", 16 | "dependsOn": [ 17 | "CMake: configure" 18 | ], 19 | "targets": [ 20 | "all" 21 | ], 22 | "group": { 23 | "kind": "build", 24 | "isDefault": true 25 | } 26 | }, 27 | { 28 | "type": "shell", 29 | "label": "CMake: build test", 30 | "command": "cmake -Bbuild && cmake --build build --parallel 4", 31 | "options": { 32 | "cwd": "${workspaceFolder}/test" 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(superfamicheck LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | include_directories(include) 7 | 8 | if(NOT CMAKE_BUILD_TYPE) 9 | set(CMAKE_BUILD_TYPE Debug) 10 | endif() 11 | 12 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 13 | set(CMAKE_OSX_ARCHITECTURES arm64 x86_64) 14 | endif() 15 | 16 | if(MSVC) 17 | add_definitions(-DNOMINMAX) 18 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") 19 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") 20 | endif() 21 | 22 | set(SOURCES src/superfamicheck.cpp src/sfcRom.cpp) 23 | add_executable(superfamicheck ${SOURCES}) 24 | 25 | if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT MSVC) 26 | set_property(TARGET superfamicheck PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) 27 | endif() 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016-2024 David Lindecrantz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR := build 2 | 3 | .PHONY: clean 4 | 5 | release: 6 | @cmake -B$(BUILD_DIR)/release -DCMAKE_BUILD_TYPE=Release 7 | @cmake --build $(BUILD_DIR)/release --parallel 4 8 | 9 | debug: 10 | @cmake -B$(BUILD_DIR)/debug -DCMAKE_BUILD_TYPE=Debug 11 | @cmake --build $(BUILD_DIR)/debug --parallel 4 12 | 13 | all: clean release 14 | 15 | clean: 16 | @rm -rf $(BUILD_DIR) 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # superfamicheck 2 | superfamicheck is a simple command line utility that shows (and optionally fixes) header info for super nintendo / super famicom ROM images. 3 | 4 | superfamicheck is programmed by David Lindecrantz and distributed under the terms of the [MIT license](./LICENSE). 5 | 6 | 7 | ## building 8 | use cmake to generate a build environment, or simply type `make` which will run cmake for you. 9 | 10 | ## operation 11 | 12 | superfamicheck rom_file [options...] 13 | 14 | the following options are available: 15 | 16 | -h, --help show usage instructions 17 | -o, --out FILE specify file to write (if -f) 18 | -f, --fix fix header (checksum/title/size) 19 | -s, --semisilent silent operation (unless issues found) 20 | -S, --silent silent operation 21 | 22 | show info for file rom.sfc: 23 | 24 | superfamicheck rom.sfc 25 | 26 | show info for file rom.sfc and fix header (replacing source file): 27 | 28 | superfamicheck rom.sfc -f 29 | 30 | show info for file rom.sfc and write fixed ROM image to specified file: 31 | 32 | superfamicheck rom.sfc -f -o fixed.sfc 33 | 34 | 35 | ## acknowledgments 36 | 37 | superfamicheck uses the following libraries: 38 | 39 | * [ezOptionParser](http://ezoptionparser.sourceforge.net) by remik ziemlinski 40 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- 1 | -xc++ 2 | -std=c++20 3 | -Wall 4 | -Wextra 5 | -Iinclude 6 | -isystem/usr/local/include 7 | -------------------------------------------------------------------------------- /include/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | "DisableFormat": true 3 | -------------------------------------------------------------------------------- /include/.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: '' 2 | -------------------------------------------------------------------------------- /include/ezOptionParser/MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011,2012 Remik Ziemlinski 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /include/ezOptionParser/README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | -------- 3 | [ezOptionParser](http://sourceforge.net/projects/ezoptionparser/) is another command-line parser class for C++ that has features not available in alternative solutions (getopt, boost, argtable, argstream, gflags) and doesn't require a steep learning curve. 4 | 5 | Download 6 | -------- 7 | [Source Code and Examples](http://sourceforge.net/projects/ezoptionparser/files/) 8 | 9 | [ezOptionParser.hpp](ezOptionParser.hpp) 10 | 11 | Features 12 | -------- 13 | - Pretty printing of parsed inputs for debugging. 14 | - Auto usage message creation in three layouts (aligned, interleaved or staggered). 15 | - Single header file implementation. 16 | - Dependent only on STL. 17 | - Arbitrary short and long option names (dash '-' or plus '+' prefixes not required). 18 | - Arbitrary argument list delimiters. 19 | - Multiple flag instances allowed. 20 | - Validation of required options, number of expected arguments per flag, datatype ranges, user defined ranges, membership in lists and case for string lists. 21 | - Validation criteria definable by strings or constants. 22 | - Multiple file import with comments. 23 | - Exports to file, either set options or all options including defaults when available. 24 | - Option parse index for order dependent contexts. 25 | - MIT license. 26 | - Minimal learning curve due to many examples. 27 | - Automated regression and memory tests with valgrind. 28 | 29 | Examples 30 | -------- 31 | [complete.cpp](complete.html) 32 | A complete example that could be used as a starting point for your own C++ program. 33 | 34 | [fileio.cpp](fileio.html) 35 | Shows how to import and export options with files (that can contain comments!). 36 | 37 | [full.cpp](full.html) 38 | A full test of all the features. Meant for testing, but can be a source of ideas for what's possible. 39 | 40 | [long.cpp](long.html) 41 | Demo of using long flag names. 42 | 43 | [multi.cpp](multi.html) 44 | Shows how to handle multiple instances of a flag. 45 | 46 | [parseindex.cpp](parseindex.html) 47 | Demo of parsed indices for options, so you can create ordered contexts. 48 | 49 | [pretty.cpp](pretty.html) 50 | Demo of pretty printing everything parsed, which can help in debugging. 51 | 52 | [short.cpp](short.html) 53 | Short demo of a short flag name. 54 | 55 | [usage.cpp](usage.html) 56 | Demo of automatic usage message creation in three builtin layouts. 57 | Here are how the three layouts appear: 58 | [aligned](aligned.html), [interleaved](interleaved.html), [staggered](staggered.html) 59 | 60 | [valid.cpp](valid.html) 61 | Demo of using validators defined by strings, which only check if values are within for their datatype limits. 62 | 63 | [validrange.cpp](validrange.html) 64 | Demo of using validators with value ranges and lists defined by strings. 65 | 66 | [validfast.cpp](validfast.html) 67 | Demo of using validators defined by constants for more efficient execution. These validators only check if values are within for their datatype limits.. 68 | 69 | [validrangefast.cpp](validrangefast.html) 70 | Demo of using validators with value ranges and lists defined by constants for more efficient execution. 71 | 72 | Usage 73 | ----- 74 | Copy or include ezOptionParser.hpp to your project and use the "ez" namespace, as shown here: 75 | 76 | // pretty.cpp 77 | #include 78 | #include "ezOptionParser.hpp" 79 | 80 | int main(int argc, const char * argv[]) { 81 | ez::ezOptionParser opt; 82 | 83 | opt.overview = "Demo of pretty printing everything parsed."; 84 | opt.syntax = "pretty [OPTIONS]"; 85 | opt.example = "pretty foo bar --debug --dummy -list 1,2,16 in1 in2 out\n\n"; 86 | opt.footer = "ezOptionParser (C) 2014\n"; 87 | 88 | opt.add( 89 | "", // Default. 90 | 0, // Required? 91 | 0, // Number of args expected. 92 | 0, // Delimiter if expecting multiple args. 93 | "Display usage instructions.", // Help description. 94 | "-h", // Flag token. 95 | "-help", // Flag token. 96 | "--help", // Flag token. 97 | "--usage" // Flag token. 98 | ); 99 | 100 | opt.add( 101 | "", // Default. 102 | 0, // Required? 103 | 0, // Number of args expected. 104 | 0, // Delimiter if expecting multiple args. 105 | "Print all inputs and categories for debugging.", // Help description. 106 | "--debug" // Flag token. 107 | ); 108 | 109 | opt.parse(argc, argv); 110 | 111 | if (opt.isSet("-h")) { 112 | std::string usage; 113 | opt.getUsage(usage); 114 | std::cout << usage; 115 | return 1; 116 | } 117 | 118 | if (opt.isSet("--debug")) { 119 | std::string pretty; 120 | opt.prettyPrint(pretty); 121 | std::cout << pretty; 122 | } 123 | 124 | return 0; 125 | } 126 | 127 | Here is the auto-generated usage message: 128 | 129 | ./pretty -h 130 | Demo of pretty printing everything parsed. 131 | 132 | USAGE: pretty [OPTIONS] 133 | 134 | OPTIONS: 135 | 136 | -h, -help, --help, --usage Display usage instructions. 137 | 138 | --debug Print all inputs and categories for debugging. 139 | 140 | EXAMPLES: 141 | 142 | pretty foo bar --debug --dummy -list 1,2,16 in1 in2 out 143 | 144 | ezOptionParser (C) 2014 145 | 146 | 147 | Testing 148 | ------- 149 | make 150 | make memtest 151 | make clean 152 | 153 | Installation 154 | ------------ 155 | sudo make install PREFIX=/usr/local 156 | 157 | Compiling 158 | --------- 159 | Jose Santiago suggests these compiler options to get a warning-free 160 | compile with g++-4.5.2 and ezOptionParser-0.2.2: 161 | 162 | g++ O6 -ggdb -ansi -Wall -Wextra -Wwrite-strings -Wcast-align \ 163 | -Wpointer-arith -Winit-self -Wuninitialized -Wformat=2 \ 164 | -Woverloaded-virtual -Wstrict-null-sentinel -Wnon-virtual-dtor \ 165 | -fno-omit-frame-pointer -fno-eliminate-unused-debug-symbols \ 166 | -fno-tree-vectorize -Wstrict-aliasing -fstrict-aliasing 167 | 168 | Distribution 169 | ------------ 170 | make html 171 | make clean 172 | make dist VER=0.2.2 173 | 174 | Publishing 175 | ---------- 176 | ssh -t rsz,ezoptionparser@shell.sourceforge.net create 177 | scp html/* ezOptionParser.hpp rsz,ezoptionparser@shell.sourceforge.net:/home/project-web/ezoptionparser/htdocs 178 | scp ../ezOptionParser-0.2.2.tar.gz rsz,ezoptionparser@shell.sourceforge.net:/home/frs/project/e/ez/ezoptionparser 179 | 180 | Changelog 181 | --------- 182 | v0.2.2 (20140504) 183 | 184 | - Jose Santiago contributed changes that cleans up a number of compile issues such as initializer list order complaints, signed/unsigned comparison complaints, and a printf issue where there was too many arguments. 185 | - Bruce Shankle contributed a crash fix in description printing for the usage message. 186 | 187 | v0.2.1 (20130506) 188 | 189 | - Added a toggle to disable extra line break between OPTIONS usage descriptions (opts->doublespace = 0;). This will pack the descriptions together like traditional usage messages, so you can see more per page. 190 | 191 | v0.2.0 (20121120) 192 | 193 | - Added parse index to options. Thanks to Michael Lachmann for the idea. 194 | 195 | v0.1.4 (20120629) 196 | 197 | - Fixed file licenses to MIT. 198 | 199 | v0.1.3 (20120603) 200 | 201 | - Changed license to MIT. 202 | - Reformatted readme to markdown. 203 | - Updated make dist target to be git friendly. 204 | 205 | v0.1.2 (20111126) 206 | 207 | - Published. 208 | 209 | v0.1.1 (20111011) 210 | 211 | - Published. 212 | 213 | v0.1.0 (20111011) 214 | 215 | - Published. 216 | 217 | v0.0.0 (20110511) 218 | 219 | - Published. 220 | 221 | License 222 | ------- 223 | Copyright 2011, 2012, 2014 Remik Ziemlinski (see MIT-LICENSE) 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /include/ezOptionParser/ezOptionParser.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of ezOptionParser. See MIT-LICENSE. 3 | 4 | Copyright (C) 2011,2012,2014 Remik Ziemlinski 5 | 6 | CHANGELOG 7 | 8 | v0.0.0 20110505 rsz Created. 9 | v0.1.0 20111006 rsz Added validator. 10 | v0.1.1 20111012 rsz Fixed validation of ulonglong. 11 | v0.1.2 20111126 rsz Allow flag names start with alphanumeric (previously, flag had to start with alpha). 12 | v0.1.3 20120108 rsz Created work-around for unique id generation with IDGenerator that avoids retarded c++ translation unit linker errors with single-header static variables. Forced inline on all methods to please retard compiler and avoid multiple def errors. 13 | v0.1.4 20120629 Enforced MIT license on all files. 14 | v0.2.0 20121120 Added parseIndex to OptionGroup. 15 | v0.2.1 20130506 Allow disabling doublespace of OPTIONS usage descriptions. 16 | v0.2.2 20140504 Jose Santiago added compiler warning fixes. 17 | Bruce Shankle added a crash fix in description printing. 18 | 20240309 David Lindcrantz replaced sprintf usage with snprintf. 19 | */ 20 | #ifndef EZ_OPTION_PARSER_H 21 | #define EZ_OPTION_PARSER_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace ez { 36 | #define DEBUGLINE() printf("%s:%d\n", __FILE__, __LINE__); 37 | 38 | /* ################################################################### */ 39 | template 40 | static T fromString(const std::string* s) { 41 | std::istringstream stream (s->c_str()); 42 | T t; 43 | stream >> t; 44 | return t; 45 | }; 46 | template 47 | static T fromString(const char* s) { 48 | std::istringstream stream (s); 49 | T t; 50 | stream >> t; 51 | return t; 52 | }; 53 | /* ################################################################### */ 54 | static inline bool isdigit(const std::string & s, int i=0) { 55 | int n = s.length(); 56 | for(; i < n; ++i) 57 | switch(s[i]) { 58 | case '0': case '1': case '2': 59 | case '3': case '4': case '5': 60 | case '6': case '7': case '8': case '9': break; 61 | default: return false; 62 | } 63 | 64 | return true; 65 | }; 66 | /* ################################################################### */ 67 | static bool isdigit(const std::string * s, int i=0) { 68 | int n = s->length(); 69 | for(; i < n; ++i) 70 | switch(s->at(i)) { 71 | case '0': case '1': case '2': 72 | case '3': case '4': case '5': 73 | case '6': case '7': case '8': case '9': break; 74 | default: return false; 75 | } 76 | 77 | return true; 78 | }; 79 | /* ################################################################### */ 80 | /* 81 | Compare strings for opts, so short opt flags come before long format flags. 82 | For example, -d < --dimension < --dmn, and also lower come before upper. The default STL std::string compare doesn't do that. 83 | */ 84 | static bool CmpOptStringPtr(std::string * s1, std::string * s2) { 85 | int c1,c2; 86 | const char *s=s1->c_str(); 87 | for(c1=0; c1 < (long int)s1->size(); ++c1) 88 | if (isalnum(s[c1])) // locale sensitive. 89 | break; 90 | 91 | s=s2->c_str(); 92 | for(c2=0; c2 < (long int)s2->size(); ++c2) 93 | if (isalnum(s[c2])) 94 | break; 95 | 96 | // Test which has more symbols before its name. 97 | if (c1 > c2) 98 | return false; 99 | else if (c1 < c2) 100 | return true; 101 | 102 | // Both have same number of symbols, so compare first letter. 103 | char char1 = s1->at(c1); 104 | char char2 = s2->at(c2); 105 | char lo1 = tolower(char1); 106 | char lo2 = tolower(char2); 107 | 108 | if (lo1 != lo2) 109 | return lo1 < lo2; 110 | 111 | // Their case doesn't match, so find which is lower. 112 | char up1 = isupper(char1); 113 | char up2 = isupper(char2); 114 | 115 | if (up1 && !up2) 116 | return false; 117 | else if (!up1 && up2) 118 | return true; 119 | 120 | return (s1->compare(*s2)<0); 121 | }; 122 | /* ################################################################### */ 123 | /* 124 | Makes a vector of strings from one string, 125 | splitting at (and excluding) delimiter "token". 126 | */ 127 | static void SplitDelim( const std::string& s, const char token, std::vector * result) { 128 | std::string::const_iterator i = s.begin(); 129 | std::string::const_iterator j = s.begin(); 130 | const std::string::const_iterator e = s.end(); 131 | 132 | while(i!=e) { 133 | while(i!=e && *i++!=token); 134 | std::string *newstr = new std::string(j, i); 135 | if (newstr->at(newstr->size()-1) == token) newstr->erase(newstr->size()-1); 136 | result->push_back(newstr); 137 | j = i; 138 | } 139 | }; 140 | /* ################################################################### */ 141 | // Variant that uses deep copies and references instead of pointers (less efficient). 142 | static void SplitDelim( const std::string& s, const char token, std::vector & result) { 143 | std::string::const_iterator i = s.begin(); 144 | std::string::const_iterator j = s.begin(); 145 | const std::string::const_iterator e = s.end(); 146 | 147 | while(i!=e) { 148 | while(i!=e && *i++!=token); 149 | std::string newstr(j, i); 150 | if (newstr.at(newstr.size()-1) == token) newstr.erase(newstr.size()-1); 151 | result.push_back(newstr); 152 | j = i; 153 | } 154 | }; 155 | /* ################################################################### */ 156 | // Variant that uses list instead of vector for efficient insertion, etc. 157 | static void SplitDelim( const std::string& s, const char token, std::list & result) { 158 | std::string::const_iterator i = s.begin(); 159 | std::string::const_iterator j = s.begin(); 160 | const std::string::const_iterator e = s.end(); 161 | 162 | while(i!=e) { 163 | while(i!=e && *i++!=token); 164 | std::string *newstr = new std::string(j, i); 165 | if (newstr->at(newstr->size()-1) == token) newstr->erase(newstr->size()-1); 166 | result.push_back(newstr); 167 | j = i; 168 | } 169 | }; 170 | /* ################################################################### */ 171 | static void ToU1(std::string ** strings, unsigned char * out, int n) { 172 | for(int i=0; i < n; ++i) { 173 | out[i] = (unsigned char)atoi(strings[i]->c_str()); 174 | } 175 | }; 176 | /* ################################################################### */ 177 | static void ToS1(std::string ** strings, char * out, int n) { 178 | for(int i=0; i < n; ++i) { 179 | out[i] = (char)atoi(strings[i]->c_str()); 180 | } 181 | }; 182 | /* ################################################################### */ 183 | static void ToU2(std::string ** strings, unsigned short * out, int n) { 184 | for(int i=0; i < n; ++i) { 185 | out[i] = (unsigned short)atoi(strings[i]->c_str()); 186 | } 187 | }; 188 | /* ################################################################### */ 189 | static void ToS2(std::string ** strings, short * out, int n) { 190 | for(int i=0; i < n; ++i) { 191 | out[i] = (short)atoi(strings[i]->c_str()); 192 | } 193 | }; 194 | /* ################################################################### */ 195 | static void ToS4(std::string ** strings, int * out, int n) { 196 | for(int i=0; i < n; ++i) { 197 | out[i] = atoi(strings[i]->c_str()); 198 | } 199 | }; 200 | /* ################################################################### */ 201 | static void ToU4(std::string ** strings, unsigned int * out, int n) { 202 | for(int i=0; i < n; ++i) { 203 | out[i] = (unsigned int)strtoul(strings[i]->c_str(), NULL, 0); 204 | } 205 | }; 206 | /* ################################################################### */ 207 | static void ToS8(std::string ** strings, long long * out, int n) { 208 | for(int i=0; i < n; ++i) { 209 | std::stringstream ss(strings[i]->c_str()); 210 | ss >> out[i]; 211 | } 212 | }; 213 | /* ################################################################### */ 214 | static void ToU8(std::string ** strings, unsigned long long * out, int n) { 215 | for(int i=0; i < n; ++i) { 216 | std::stringstream ss(strings[i]->c_str()); 217 | ss >> out[i]; 218 | } 219 | }; 220 | /* ################################################################### */ 221 | static void ToF(std::string ** strings, float * out, int n) { 222 | for(int i=0; i < n; ++i) { 223 | out[i] = (float)atof(strings[i]->c_str()); 224 | } 225 | }; 226 | /* ################################################################### */ 227 | static void ToD(std::string ** strings, double * out, int n) { 228 | for(int i=0; i < n; ++i) { 229 | out[i] = (double)atof(strings[i]->c_str()); 230 | } 231 | }; 232 | /* ################################################################### */ 233 | static void StringsToInts(std::vector & strings, std::vector & out) { 234 | for(int i=0; i < (long int)strings.size(); ++i) { 235 | out.push_back(atoi(strings[i].c_str())); 236 | } 237 | }; 238 | /* ################################################################### */ 239 | static void StringsToInts(std::vector * strings, std::vector * out) { 240 | for(int i=0; i < (long int)strings->size(); ++i) { 241 | out->push_back(atoi(strings->at(i)->c_str())); 242 | } 243 | }; 244 | /* ################################################################### */ 245 | static void StringsToLongs(std::vector & strings, std::vector & out) { 246 | for(int i=0; i < (long int)strings.size(); ++i) { 247 | out.push_back(atol(strings[i].c_str())); 248 | } 249 | }; 250 | /* ################################################################### */ 251 | static void StringsToLongs(std::vector * strings, std::vector * out) { 252 | for(int i=0; i < (long int)strings->size(); ++i) { 253 | out->push_back(atol(strings->at(i)->c_str())); 254 | } 255 | }; 256 | /* ################################################################### */ 257 | static void StringsToULongs(std::vector & strings, std::vector & out) { 258 | for(int i=0; i < (long int)strings.size(); ++i) { 259 | out.push_back(strtoul(strings[i].c_str(),0,0)); 260 | } 261 | }; 262 | /* ################################################################### */ 263 | static void StringsToULongs(std::vector * strings, std::vector * out) { 264 | for(int i=0; i < (long int)strings->size(); ++i) { 265 | out->push_back(strtoul(strings->at(i)->c_str(),0,0)); 266 | } 267 | }; 268 | /* ################################################################### */ 269 | static void StringsToFloats(std::vector & strings, std::vector & out) { 270 | for(int i=0; i < (long int)strings.size(); ++i) { 271 | out.push_back(atof(strings[i].c_str())); 272 | } 273 | }; 274 | /* ################################################################### */ 275 | static void StringsToFloats(std::vector * strings, std::vector * out) { 276 | for(int i=0; i < (long int)strings->size(); ++i) { 277 | out->push_back(atof(strings->at(i)->c_str())); 278 | } 279 | }; 280 | /* ################################################################### */ 281 | static void StringsToDoubles(std::vector & strings, std::vector & out) { 282 | for(int i=0; i < (long int)strings.size(); ++i) { 283 | out.push_back(atof(strings[i].c_str())); 284 | } 285 | }; 286 | /* ################################################################### */ 287 | static void StringsToDoubles(std::vector * strings, std::vector * out) { 288 | for(int i=0; i < (long int)strings->size(); ++i) { 289 | out->push_back(atof(strings->at(i)->c_str())); 290 | } 291 | }; 292 | /* ################################################################### */ 293 | static void StringsToStrings(std::vector * strings, std::vector * out) { 294 | for(int i=0; i < (long int)strings->size(); ++i) { 295 | out->push_back( *strings->at(i) ); 296 | } 297 | }; 298 | /* ################################################################### */ 299 | static void ToLowerASCII(std::string & s) { 300 | int n = s.size(); 301 | int i=0; 302 | char c; 303 | for(; i < n; ++i) { 304 | c = s[i]; 305 | if(c<='Z' && c>='A') 306 | s[i] = c+32; 307 | } 308 | } 309 | /* ################################################################### */ 310 | static char** CommandLineToArgvA(char* CmdLine, int* _argc) { 311 | char** argv; 312 | char* _argv; 313 | unsigned long len; 314 | unsigned long argc; 315 | char a; 316 | unsigned long i, j; 317 | 318 | bool in_QM; 319 | bool in_TEXT; 320 | bool in_SPACE; 321 | 322 | len = strlen(CmdLine); 323 | i = ((len+2)/2)*sizeof(void*) + sizeof(void*); 324 | 325 | argv = (char**)malloc(i + (len+2)*sizeof(char)); 326 | 327 | _argv = (char*)(((unsigned char*)argv)+i); 328 | 329 | argc = 0; 330 | argv[argc] = _argv; 331 | in_QM = false; 332 | in_TEXT = false; 333 | in_SPACE = true; 334 | i = 0; 335 | j = 0; 336 | 337 | while( (a = CmdLine[i]) ) { 338 | if(in_QM) { 339 | if( (a == '\"') || 340 | (a == '\'')) // rsz. Added single quote. 341 | { 342 | in_QM = false; 343 | } else { 344 | _argv[j] = a; 345 | j++; 346 | } 347 | } else { 348 | switch(a) { 349 | case '\"': 350 | case '\'': // rsz. Added single quote. 351 | in_QM = true; 352 | in_TEXT = true; 353 | if(in_SPACE) { 354 | argv[argc] = _argv+j; 355 | argc++; 356 | } 357 | in_SPACE = false; 358 | break; 359 | case ' ': 360 | case '\t': 361 | case '\n': 362 | case '\r': 363 | if(in_TEXT) { 364 | _argv[j] = '\0'; 365 | j++; 366 | } 367 | in_TEXT = false; 368 | in_SPACE = true; 369 | break; 370 | default: 371 | in_TEXT = true; 372 | if(in_SPACE) { 373 | argv[argc] = _argv+j; 374 | argc++; 375 | } 376 | _argv[j] = a; 377 | j++; 378 | in_SPACE = false; 379 | break; 380 | } 381 | } 382 | i++; 383 | } 384 | _argv[j] = '\0'; 385 | argv[argc] = NULL; 386 | 387 | (*_argc) = argc; 388 | return argv; 389 | }; 390 | /* ################################################################### */ 391 | // Create unique ids with static and still allow single header that avoids multiple definitions linker error. 392 | class ezOptionParserIDGenerator { 393 | public: 394 | static ezOptionParserIDGenerator& instance () { static ezOptionParserIDGenerator Generator; return Generator; } 395 | short next () { return ++_id; } 396 | private: 397 | ezOptionParserIDGenerator() : _id(-1) {} 398 | short _id; 399 | }; 400 | /* ################################################################### */ 401 | /* Validate a value by checking: 402 | - if as string, see if converted value is within datatype's limits, 403 | - and see if falls within a desired range, 404 | - or see if within set of given list of values. 405 | 406 | If comparing with a range, the values list must contain one or two values. One value is required when comparing with <, <=, >, >=. Use two values when requiring a test such as list[0] */ 435 | GE, /* value >= list[0] */ 436 | GTLT, /* list[0] < value < list[1] */ 437 | GELT, /* list[0] <= value < list[1] */ 438 | GELE, /* list[0] <= value <= list[1] */ 439 | GTLE, /* list[0] < value <= list[1] */ 440 | IN /* if value is in list */ 441 | }; 442 | 443 | enum TYPE { NOTYPE=0, S1, U1, S2, U2, S4, U4, S8, U8, F, D, T }; 444 | enum TYPE2 { NOTYPE2=0, INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64, FLOAT, DOUBLE, TEXT }; 445 | 446 | union { 447 | unsigned char *u1; 448 | char *s1; 449 | unsigned short *u2; 450 | short *s2; 451 | unsigned int *u4; 452 | int *s4; 453 | unsigned long long *u8; 454 | long long *s8; 455 | float *f; 456 | double *d; 457 | std::string** t; 458 | }; 459 | 460 | char op; 461 | bool quiet; 462 | short id; 463 | char type; 464 | int size; 465 | bool insensitive; 466 | }; 467 | /* ------------------------------------------------------------------- */ 468 | ezOptionValidator::~ezOptionValidator() { 469 | reset(); 470 | }; 471 | /* ------------------------------------------------------------------- */ 472 | void ezOptionValidator::reset() { 473 | #define CLEAR(TYPE,P) case TYPE: if (P) delete [] P; P = 0; break; 474 | switch(type) { 475 | CLEAR(S1,s1); 476 | CLEAR(U1,u1); 477 | CLEAR(S2,s2); 478 | CLEAR(U2,u2); 479 | CLEAR(S4,s4); 480 | CLEAR(U4,u4); 481 | CLEAR(S8,s8); 482 | CLEAR(U8,u8); 483 | CLEAR(F,f); 484 | CLEAR(D,d); 485 | case T: 486 | for(int i=0; i < size; ++i) 487 | delete t[i]; 488 | 489 | delete [] t; 490 | t = 0; 491 | break; 492 | default: break; 493 | } 494 | 495 | size = 0; 496 | op = NOOP; 497 | type = NOTYPE; 498 | }; 499 | /* ------------------------------------------------------------------- */ 500 | ezOptionValidator::ezOptionValidator(char _type) : s1(0), op(0), quiet(0), type(_type), size(0), insensitive(0) { 501 | id = ezOptionParserIDGenerator::instance().next(); 502 | }; 503 | /* ------------------------------------------------------------------- */ 504 | ezOptionValidator::ezOptionValidator(char _type, char _op, const char* list, int _size) : s1(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 505 | id = ezOptionParserIDGenerator::instance().next(); 506 | s1 = new char[size]; 507 | memcpy(s1, list, size); 508 | }; 509 | /* ------------------------------------------------------------------- */ 510 | ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned char* list, int _size) : u1(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 511 | id = ezOptionParserIDGenerator::instance().next(); 512 | u1 = new unsigned char[size]; 513 | memcpy(u1, list, size); 514 | }; 515 | /* ------------------------------------------------------------------- */ 516 | ezOptionValidator::ezOptionValidator(char _type, char _op, const short* list, int _size) : s2(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 517 | id = ezOptionParserIDGenerator::instance().next(); 518 | s2 = new short[size]; 519 | memcpy(s2, list, size*sizeof(short)); 520 | }; 521 | /* ------------------------------------------------------------------- */ 522 | ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned short* list, int _size) : u2(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 523 | id = ezOptionParserIDGenerator::instance().next(); 524 | u2 = new unsigned short[size]; 525 | memcpy(u2, list, size*sizeof(unsigned short)); 526 | }; 527 | /* ------------------------------------------------------------------- */ 528 | ezOptionValidator::ezOptionValidator(char _type, char _op, const int* list, int _size) : s4(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 529 | id = ezOptionParserIDGenerator::instance().next(); 530 | s4 = new int[size]; 531 | memcpy(s4, list, size*sizeof(int)); 532 | }; 533 | /* ------------------------------------------------------------------- */ 534 | ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned int* list, int _size) : u4(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 535 | id = ezOptionParserIDGenerator::instance().next(); 536 | u4 = new unsigned int[size]; 537 | memcpy(u4, list, size*sizeof(unsigned int)); 538 | }; 539 | /* ------------------------------------------------------------------- */ 540 | ezOptionValidator::ezOptionValidator(char _type, char _op, const long long* list, int _size) : s8(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 541 | id = ezOptionParserIDGenerator::instance().next(); 542 | s8 = new long long[size]; 543 | memcpy(s8, list, size*sizeof(long long)); 544 | }; 545 | /* ------------------------------------------------------------------- */ 546 | ezOptionValidator::ezOptionValidator(char _type, char _op, const unsigned long long* list, int _size) : u8(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 547 | id = ezOptionParserIDGenerator::instance().next(); 548 | u8 = new unsigned long long[size]; 549 | memcpy(u8, list, size*sizeof(unsigned long long)); 550 | }; 551 | /* ------------------------------------------------------------------- */ 552 | ezOptionValidator::ezOptionValidator(char _type, char _op, const float* list, int _size) : f(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 553 | id = ezOptionParserIDGenerator::instance().next(); 554 | f = new float[size]; 555 | memcpy(f, list, size*sizeof(float)); 556 | }; 557 | /* ------------------------------------------------------------------- */ 558 | ezOptionValidator::ezOptionValidator(char _type, char _op, const double* list, int _size) : d(0), op(_op), quiet(0), type(_type), size(_size), insensitive(0) { 559 | id = ezOptionParserIDGenerator::instance().next(); 560 | d = new double[size]; 561 | memcpy(d, list, size*sizeof(double)); 562 | }; 563 | /* ------------------------------------------------------------------- */ 564 | ezOptionValidator::ezOptionValidator(char _type, char _op, const char** list, int _size, bool _insensitive) : t(0), op(_op), quiet(0), type(_type), size(_size), insensitive(_insensitive) { 565 | id = ezOptionParserIDGenerator::instance().next(); 566 | t = new std::string*[size]; 567 | int i=0; 568 | 569 | for(; i < size; ++i) { 570 | t[i] = new std::string(list[i]); 571 | } 572 | }; 573 | /* ------------------------------------------------------------------- */ 574 | /* Less efficient but convenient ctor that parses strings to setup validator. 575 | _type: s1, u1, s2, u2, ..., f, d, t 576 | _op: lt, gt, ..., in 577 | _list: comma-delimited string 578 | */ 579 | ezOptionValidator::ezOptionValidator(const char* _type, const char* _op, const char* _list, bool _insensitive) : t(0), quiet(0), type(0), size(0), insensitive(_insensitive) { 580 | id = ezOptionParserIDGenerator::instance().next(); 581 | 582 | switch(_type[0]) { 583 | case 'u': 584 | switch(_type[1]) { 585 | case '1': type = U1; break; 586 | case '2': type = U2; break; 587 | case '4': type = U4; break; 588 | case '8': type = U8; break; 589 | default: break; 590 | } 591 | break; 592 | case 's': 593 | switch(_type[1]) { 594 | case '1': type = S1; 595 | break; 596 | case '2': type = S2; break; 597 | case '4': type = S4; break; 598 | case '8': type = S8; break; 599 | default: break; 600 | } 601 | break; 602 | case 'f': type = F; break; 603 | case 'd': type = D; break; 604 | case 't': type = T; break; 605 | default: 606 | if (!quiet) 607 | std::cerr << "ERROR: Unknown validator datatype \"" << _type << "\".\n"; 608 | break; 609 | } 610 | 611 | int nop = 0; 612 | if (_op != 0) 613 | nop = strlen(_op); 614 | 615 | switch(nop) { 616 | case 0: op = NOOP; break; 617 | case 2: 618 | switch(_op[0]) { 619 | case 'g': 620 | switch(_op[1]) { 621 | case 'e': op = GE; break; 622 | default: op = GT; break; 623 | } 624 | break; 625 | case 'i': op = IN; 626 | break; 627 | default: 628 | switch(_op[1]) { 629 | case 'e': op = LE; break; 630 | default: op = LT; break; 631 | } 632 | break; 633 | } 634 | break; 635 | case 4: 636 | switch(_op[1]) { 637 | case 'e': 638 | switch(_op[3]) { 639 | case 'e': op = GELE; break; 640 | default: op = GELT; break; 641 | } 642 | break; 643 | default: 644 | switch(_op[3]) { 645 | case 'e': op = GTLE; break; 646 | default: op = GTLT; break; 647 | } 648 | break; 649 | } 650 | break; 651 | default: 652 | if (!quiet) 653 | std::cerr << "ERROR: Unknown validator operation \"" << _op << "\".\n"; 654 | break; 655 | } 656 | 657 | if (_list == 0) return; 658 | // Create list of strings and then cast to native datatypes. 659 | std::string unsplit(_list); 660 | std::list split; 661 | std::list::iterator it; 662 | SplitDelim(unsplit, ',', split); 663 | size = split.size(); 664 | std::string **strings = new std::string*[size]; 665 | 666 | int i = 0; 667 | for(it = split.begin(); it != split.end(); ++it) 668 | strings[i++] = *it; 669 | 670 | if (insensitive) 671 | for(i=0; i < size; ++i) 672 | ToLowerASCII(*strings[i]); 673 | 674 | #define FreeStrings() { \ 675 | for(i=0; i < size; ++i)\ 676 | delete strings[i];\ 677 | delete [] strings;\ 678 | } 679 | 680 | #define ToArray(T,P,Y) case T: P = new Y[size]; To##T(strings, P, size); FreeStrings(); break; 681 | switch(type) { 682 | ToArray(S1,s1,char); 683 | ToArray(U1,u1,unsigned char); 684 | ToArray(S2,s2,short); 685 | ToArray(U2,u2,unsigned short); 686 | ToArray(S4,s4,int); 687 | ToArray(U4,u4,unsigned int); 688 | ToArray(S8,s8,long long); 689 | ToArray(U8,u8,unsigned long long); 690 | ToArray(F,f,float); 691 | ToArray(D,d,double); 692 | case T: t = strings; break; /* Don't erase strings array. */ 693 | default: break; 694 | } 695 | }; 696 | /* ------------------------------------------------------------------- */ 697 | void ezOptionValidator::print() { 698 | printf("id=%d, op=%d, type=%d, size=%d, insensitive=%d\n", id, op, type, size, insensitive); 699 | }; 700 | /* ------------------------------------------------------------------- */ 701 | bool ezOptionValidator::isValid(const std::string * valueAsString) { 702 | if (valueAsString == 0) return false; 703 | 704 | #define CHECKRANGE(E,T) {\ 705 | std::stringstream ss(valueAsString->c_str()); \ 706 | long long E##value; \ 707 | ss >> E##value; \ 708 | long long E##min = static_cast(std::numeric_limits::min()); \ 709 | if (E##value < E##min) { \ 710 | if (!quiet) \ 711 | std::cerr << "ERROR: Invalid value " << E##value << " is less than datatype min " << E##min << ".\n"; \ 712 | return false; \ 713 | } \ 714 | \ 715 | long long E##max = static_cast(std::numeric_limits::max()); \ 716 | if (E##value > E##max) { \ 717 | if (!quiet) \ 718 | std::cerr << "ERROR: Invalid value " << E##value << " is greater than datatype max " << E##max << ".\n"; \ 719 | return false; \ 720 | } \ 721 | } 722 | // Check if within datatype limits. 723 | if (type != T) { 724 | switch(type) { 725 | case S1: CHECKRANGE(S1,char); break; 726 | case U1: CHECKRANGE(U1,unsigned char); break; 727 | case S2: CHECKRANGE(S2,short); break; 728 | case U2: CHECKRANGE(U2,unsigned short); break; 729 | case S4: CHECKRANGE(S4,int); break; 730 | case U4: CHECKRANGE(U4,unsigned int); break; 731 | case S8: { 732 | if ( (valueAsString->at(0) == '-') && 733 | isdigit(valueAsString,1) && 734 | (valueAsString->size() > 19) && 735 | (valueAsString->compare(1, 19, "9223372036854775808") > 0) ) { 736 | if (!quiet) 737 | std::cerr << "ERROR: Invalid value " << *valueAsString << " is less than datatype min -9223372036854775808.\n"; 738 | return false; 739 | } 740 | 741 | if (isdigit(valueAsString) && 742 | (valueAsString->size() > 18) && 743 | valueAsString->compare("9223372036854775807") > 0) { 744 | if (!quiet) 745 | std::cerr << "ERROR: Invalid value " << *valueAsString << " is greater than datatype max 9223372036854775807.\n"; 746 | return false; 747 | } 748 | } break; 749 | case U8: { 750 | if (valueAsString->compare("0") < 0) { 751 | if (!quiet) 752 | std::cerr << "ERROR: Invalid value " << *valueAsString << " is less than datatype min 0.\n"; 753 | return false; 754 | } 755 | 756 | if (isdigit(valueAsString) && 757 | (valueAsString->size() > 19) && 758 | valueAsString->compare("18446744073709551615") > 0) { 759 | if (!quiet) 760 | std::cerr << "ERROR: Invalid value " << *valueAsString << " is greater than datatype max 18446744073709551615.\n"; 761 | return false; 762 | } 763 | } break; 764 | case F: { 765 | double dmax = static_cast(std::numeric_limits::max()); 766 | double dvalue = atof(valueAsString->c_str()); 767 | double dmin = -dmax; 768 | if (dvalue < dmin) { 769 | if (!quiet) { 770 | fprintf(stderr, "ERROR: Invalid value %g is less than datatype min %g.\n", dvalue, dmin); 771 | } 772 | return false; 773 | } 774 | 775 | if (dvalue > dmax) { 776 | if (!quiet) 777 | std::cerr << "ERROR: Invalid value " << dvalue << " is greater than datatype max " << dmax << ".\n"; 778 | return false; 779 | } 780 | } break; 781 | case D: { 782 | long double ldmax = static_cast(std::numeric_limits::max()); 783 | std::stringstream ss(valueAsString->c_str()); 784 | long double ldvalue; 785 | ss >> ldvalue; 786 | long double ldmin = -ldmax; 787 | 788 | if (ldvalue < ldmin) { 789 | if (!quiet) 790 | std::cerr << "ERROR: Invalid value " << ldvalue << " is less than datatype min " << ldmin << ".\n"; 791 | return false; 792 | } 793 | 794 | if (ldvalue > ldmax) { 795 | if (!quiet) 796 | std::cerr << "ERROR: Invalid value " << ldvalue << " is greater than datatype max " << ldmax << ".\n"; 797 | return false; 798 | } 799 | } break; 800 | case NOTYPE: default: break; 801 | } 802 | } else { 803 | if (op == IN) { 804 | int i=0; 805 | if (insensitive) { 806 | std::string valueAsStringLower(*valueAsString); 807 | ToLowerASCII(valueAsStringLower); 808 | for(; i < size; ++i) { 809 | if (valueAsStringLower.compare(t[i]->c_str()) == 0) 810 | return true; 811 | } 812 | } else { 813 | for(; i < size; ++i) { 814 | if (valueAsString->compare(t[i]->c_str()) == 0) 815 | return true; 816 | } 817 | } 818 | return false; 819 | } 820 | } 821 | 822 | // Only check datatype limits, and return; 823 | if (op == NOOP) return true; 824 | 825 | #define VALIDATE(T, U, LIST) { \ 826 | /* Value string converted to true native type. */ \ 827 | std::stringstream ss(valueAsString->c_str());\ 828 | U v;\ 829 | ss >> v;\ 830 | /* Check if within list. */ \ 831 | if (op == IN) { \ 832 | T * last = LIST + size;\ 833 | return (last != std::find(LIST, last, v)); \ 834 | } \ 835 | \ 836 | /* Check if within user's custom range. */ \ 837 | T v0, v1; \ 838 | if (size > 0) { \ 839 | v0 = LIST[0]; \ 840 | } \ 841 | \ 842 | if (size > 1) { \ 843 | v1 = LIST[1]; \ 844 | } \ 845 | \ 846 | switch (op) {\ 847 | case LT:\ 848 | if (size > 0) {\ 849 | return v < v0;\ 850 | } else {\ 851 | std::cerr << "ERROR: No value given to validate if " << v << " < X.\n";\ 852 | return false;\ 853 | }\ 854 | break;\ 855 | case LE:\ 856 | if (size > 0) {\ 857 | return v <= v0;\ 858 | } else {\ 859 | std::cerr << "ERROR: No value given to validate if " << v << " <= X.\n";\ 860 | return false;\ 861 | }\ 862 | break;\ 863 | case GT:\ 864 | if (size > 0) {\ 865 | return v > v0;\ 866 | } else {\ 867 | std::cerr << "ERROR: No value given to validate if " << v << " > X.\n";\ 868 | return false;\ 869 | }\ 870 | break;\ 871 | case GE:\ 872 | if (size > 0) {\ 873 | return v >= v0;\ 874 | } else {\ 875 | std::cerr << "ERROR: No value given to validate if " << v << " >= X.\n";\ 876 | return false;\ 877 | }\ 878 | break;\ 879 | case GTLT:\ 880 | if (size > 1) {\ 881 | return (v0 < v) && (v < v1);\ 882 | } else {\ 883 | std::cerr << "ERROR: Missing values to validate if X1 < " << v << " < X2.\n";\ 884 | return false;\ 885 | }\ 886 | break;\ 887 | case GELT:\ 888 | if (size > 1) {\ 889 | return (v0 <= v) && (v < v1);\ 890 | } else {\ 891 | std::cerr << "ERROR: Missing values to validate if X1 <= " << v << " < X2.\n";\ 892 | return false;\ 893 | }\ 894 | break;\ 895 | case GELE:\ 896 | if (size > 1) {\ 897 | return (v0 <= v) && (v <= v1);\ 898 | } else {\ 899 | std::cerr << "ERROR: Missing values to validate if X1 <= " << v << " <= X2.\n";\ 900 | return false;\ 901 | }\ 902 | break;\ 903 | case GTLE:\ 904 | if (size > 1) {\ 905 | return (v0 < v) && (v <= v1);\ 906 | } else {\ 907 | std::cerr << "ERROR: Missing values to validate if X1 < " << v << " <= X2.\n";\ 908 | return false;\ 909 | }\ 910 | break;\ 911 | case NOOP: case IN: default: break;\ 912 | } \ 913 | } 914 | 915 | switch(type) { 916 | case U1: VALIDATE(unsigned char, int, u1); break; 917 | case S1: VALIDATE(char, int, s1); break; 918 | case U2: VALIDATE(unsigned short, int, u2); break; 919 | case S2: VALIDATE(short, int, s2); break; 920 | case U4: VALIDATE(unsigned int, unsigned int, u4); break; 921 | case S4: VALIDATE(int, int, s4); break; 922 | case U8: VALIDATE(unsigned long long, unsigned long long, u8); break; 923 | case S8: VALIDATE(long long, long long, s8); break; 924 | case F: VALIDATE(float, float, f); break; 925 | case D: VALIDATE(double, double, d); break; 926 | default: break; 927 | } 928 | 929 | return true; 930 | }; 931 | /* ################################################################### */ 932 | class OptionGroup { 933 | public: 934 | OptionGroup() : delim(0), expectArgs(0), isRequired(false), isSet(false) { } 935 | 936 | ~OptionGroup() { 937 | int i; 938 | for(i=0; i < (long int)flags.size(); ++i) 939 | delete flags[i]; 940 | 941 | flags.clear(); 942 | parseIndex.clear(); 943 | clearArgs(); 944 | }; 945 | 946 | inline void clearArgs(); 947 | inline void getInt(int&); 948 | inline void getLong(long&); 949 | inline void getLongLong(long long&); 950 | inline void getULong(unsigned long&); 951 | inline void getULongLong(unsigned long long&); 952 | inline void getFloat(float&); 953 | inline void getDouble(double&); 954 | inline void getString(std::string&); 955 | inline void getInts(std::vector&); 956 | inline void getLongs(std::vector&); 957 | inline void getULongs(std::vector&); 958 | inline void getFloats(std::vector&); 959 | inline void getDoubles(std::vector&); 960 | inline void getStrings(std::vector&); 961 | inline void getMultiInts(std::vector< std::vector >&); 962 | inline void getMultiLongs(std::vector< std::vector >&); 963 | inline void getMultiULongs(std::vector< std::vector >&); 964 | inline void getMultiFloats(std::vector< std::vector >&); 965 | inline void getMultiDoubles(std::vector< std::vector >&); 966 | inline void getMultiStrings(std::vector< std::vector >&); 967 | 968 | // defaults value regardless of being set by user. 969 | std::string defaults; 970 | // If expects arguments, this will delimit arg list. 971 | char delim; 972 | // If not 0, then number of delimited args. -1 for arbitrary number. 973 | int expectArgs; 974 | // Descriptive help message shown in usage instructions for option. 975 | std::string help; 976 | // 0 or 1. 977 | bool isRequired; 978 | // A list of flags that denote this option, i.e. -d, --dimension. 979 | std::vector< std::string* > flags; 980 | // If was set (or found). 981 | bool isSet; 982 | // Lists of arguments, per flag instance, after splitting by delimiter. 983 | std::vector< std::vector< std::string* > * > args; 984 | // Index where each group was parsed from input stream to track order. 985 | std::vector parseIndex; 986 | }; 987 | /* ################################################################### */ 988 | void OptionGroup::clearArgs() { 989 | int i,j; 990 | for(i=0; i < (long int)args.size(); ++i) { 991 | for(j=0; j < (long int)args[i]->size(); ++j) 992 | delete args[i]->at(j); 993 | 994 | delete args[i]; 995 | } 996 | 997 | args.clear(); 998 | isSet = false; 999 | }; 1000 | /* ################################################################### */ 1001 | void OptionGroup::getInt(int & out) { 1002 | if (!isSet) { 1003 | if (defaults.empty()) 1004 | out = 0; 1005 | else 1006 | out = atoi(defaults.c_str()); 1007 | } else { 1008 | if (args.empty() || args[0]->empty()) 1009 | out = 0; 1010 | else { 1011 | out = atoi(args[0]->at(0)->c_str()); 1012 | } 1013 | } 1014 | }; 1015 | /* ################################################################### */ 1016 | void OptionGroup::getLong(long & out) { 1017 | if (!isSet) { 1018 | if (defaults.empty()) 1019 | out = 0; 1020 | else 1021 | out = atoi(defaults.c_str()); 1022 | } else { 1023 | if (args.empty() || args[0]->empty()) 1024 | out = 0; 1025 | else { 1026 | out = atol(args[0]->at(0)->c_str()); 1027 | } 1028 | } 1029 | }; 1030 | /* ################################################################### */ 1031 | void OptionGroup::getLongLong(long long & out) { 1032 | if (!isSet) { 1033 | if (defaults.empty()) 1034 | out = 0; 1035 | else { 1036 | std::stringstream ss(defaults.c_str()); 1037 | ss >> out; 1038 | } 1039 | } else { 1040 | if (args.empty() || args[0]->empty()) 1041 | out = 0; 1042 | else { 1043 | std::stringstream ss(args[0]->at(0)->c_str()); 1044 | ss >> out; 1045 | } 1046 | } 1047 | }; 1048 | /* ################################################################### */ 1049 | void OptionGroup::getULong(unsigned long & out) { 1050 | if (!isSet) { 1051 | if (defaults.empty()) 1052 | out = 0; 1053 | else 1054 | out = atoi(defaults.c_str()); 1055 | } else { 1056 | if (args.empty() || args[0]->empty()) 1057 | out = 0; 1058 | else { 1059 | out = strtoul(args[0]->at(0)->c_str(),0,0); 1060 | } 1061 | } 1062 | }; 1063 | /* ################################################################### */ 1064 | void OptionGroup::getULongLong(unsigned long long & out) { 1065 | if (!isSet) { 1066 | if (defaults.empty()) 1067 | out = 0; 1068 | else { 1069 | std::stringstream ss(defaults.c_str()); 1070 | ss >> out; 1071 | } 1072 | } else { 1073 | if (args.empty() || args[0]->empty()) 1074 | out = 0; 1075 | else { 1076 | std::stringstream ss(args[0]->at(0)->c_str()); 1077 | ss >> out; 1078 | } 1079 | } 1080 | }; 1081 | /* ################################################################### */ 1082 | void OptionGroup::getFloat(float & out) { 1083 | if (!isSet) { 1084 | if (defaults.empty()) 1085 | out = 0.0; 1086 | else 1087 | out = (float)atof(defaults.c_str()); 1088 | } else { 1089 | if (args.empty() || args[0]->empty()) 1090 | out = 0.0; 1091 | else { 1092 | out = (float)atof(args[0]->at(0)->c_str()); 1093 | } 1094 | } 1095 | }; 1096 | /* ################################################################### */ 1097 | void OptionGroup::getDouble(double & out) { 1098 | if (!isSet) { 1099 | if (defaults.empty()) 1100 | out = 0.0; 1101 | else 1102 | out = atof(defaults.c_str()); 1103 | } else { 1104 | if (args.empty() || args[0]->empty()) 1105 | out = 0.0; 1106 | else { 1107 | out = atof(args[0]->at(0)->c_str()); 1108 | } 1109 | } 1110 | }; 1111 | /* ################################################################### */ 1112 | void OptionGroup::getString(std::string & out) { 1113 | if (!isSet) { 1114 | out = defaults; 1115 | } else { 1116 | if (args.empty() || args[0]->empty()) 1117 | out = ""; 1118 | else { 1119 | out = *args[0]->at(0); 1120 | } 1121 | } 1122 | }; 1123 | /* ################################################################### */ 1124 | void OptionGroup::getInts(std::vector & out) { 1125 | if (!isSet) { 1126 | if (!defaults.empty()) { 1127 | std::vector< std::string > strings; 1128 | SplitDelim(defaults, delim, strings); 1129 | StringsToInts(strings, out); 1130 | } 1131 | } else { 1132 | if (!(args.empty() || args[0]->empty())) 1133 | StringsToInts(args[0], &out); 1134 | } 1135 | }; 1136 | /* ################################################################### */ 1137 | void OptionGroup::getLongs(std::vector & out) { 1138 | if (!isSet) { 1139 | if (!defaults.empty()) { 1140 | std::vector< std::string > strings; 1141 | SplitDelim(defaults, delim, strings); 1142 | StringsToLongs(strings, out); 1143 | } 1144 | } else { 1145 | if (!(args.empty() || args[0]->empty())) 1146 | StringsToLongs(args[0], &out); 1147 | } 1148 | }; 1149 | /* ################################################################### */ 1150 | void OptionGroup::getULongs(std::vector & out) { 1151 | if (!isSet) { 1152 | if (!defaults.empty()) { 1153 | std::vector< std::string > strings; 1154 | SplitDelim(defaults, delim, strings); 1155 | StringsToULongs(strings, out); 1156 | } 1157 | } else { 1158 | if (!(args.empty() || args[0]->empty())) 1159 | StringsToULongs(args[0], &out); 1160 | } 1161 | }; 1162 | /* ################################################################### */ 1163 | void OptionGroup::getFloats(std::vector & out) { 1164 | if (!isSet) { 1165 | if (!defaults.empty()) { 1166 | std::vector< std::string > strings; 1167 | SplitDelim(defaults, delim, strings); 1168 | StringsToFloats(strings, out); 1169 | } 1170 | } else { 1171 | if (!(args.empty() || args[0]->empty())) 1172 | StringsToFloats(args[0], &out); 1173 | } 1174 | }; 1175 | /* ################################################################### */ 1176 | void OptionGroup::getDoubles(std::vector & out) { 1177 | if (!isSet) { 1178 | if (!defaults.empty()) { 1179 | std::vector< std::string > strings; 1180 | SplitDelim(defaults, delim, strings); 1181 | StringsToDoubles(strings, out); 1182 | } 1183 | } else { 1184 | if (!(args.empty() || args[0]->empty())) 1185 | StringsToDoubles(args[0], &out); 1186 | } 1187 | }; 1188 | /* ################################################################### */ 1189 | void OptionGroup::getStrings(std::vector& out) { 1190 | if (!isSet) { 1191 | if (!defaults.empty()) { 1192 | SplitDelim(defaults, delim, out); 1193 | } 1194 | } else { 1195 | if (!(args.empty() || args[0]->empty())) 1196 | StringsToStrings(args[0], &out); 1197 | } 1198 | }; 1199 | /* ################################################################### */ 1200 | void OptionGroup::getMultiInts(std::vector< std::vector >& out) { 1201 | if (!isSet) { 1202 | if (!defaults.empty()) { 1203 | std::vector< std::string > strings; 1204 | SplitDelim(defaults, delim, strings); 1205 | if (out.size() < 1) out.resize(1); 1206 | StringsToInts(strings, out[0]); 1207 | } 1208 | } else { 1209 | if (!args.empty()) { 1210 | int n = args.size(); 1211 | if ((long int)out.size() < n) out.resize(n); 1212 | for(int i=0; i < n; ++i) { 1213 | StringsToInts(args[i], &out[i]); 1214 | } 1215 | } 1216 | } 1217 | }; 1218 | /* ################################################################### */ 1219 | void OptionGroup::getMultiLongs(std::vector< std::vector >& out) { 1220 | if (!isSet) { 1221 | if (!defaults.empty()) { 1222 | std::vector< std::string > strings; 1223 | SplitDelim(defaults, delim, strings); 1224 | if (out.size() < 1) out.resize(1); 1225 | StringsToLongs(strings, out[0]); 1226 | } 1227 | } else { 1228 | if (!args.empty()) { 1229 | int n = args.size(); 1230 | if ((long int)out.size() < n) out.resize(n); 1231 | for(int i=0; i < n; ++i) { 1232 | StringsToLongs(args[i], &out[i]); 1233 | } 1234 | } 1235 | } 1236 | }; 1237 | /* ################################################################### */ 1238 | void OptionGroup::getMultiULongs(std::vector< std::vector >& out) { 1239 | if (!isSet) { 1240 | if (!defaults.empty()) { 1241 | std::vector< std::string > strings; 1242 | SplitDelim(defaults, delim, strings); 1243 | if (out.size() < 1) out.resize(1); 1244 | StringsToULongs(strings, out[0]); 1245 | } 1246 | } else { 1247 | if (!args.empty()) { 1248 | int n = args.size(); 1249 | if ((long int)out.size() < n) out.resize(n); 1250 | for(int i=0; i < n; ++i) { 1251 | StringsToULongs(args[i], &out[i]); 1252 | } 1253 | } 1254 | } 1255 | }; 1256 | /* ################################################################### */ 1257 | void OptionGroup::getMultiFloats(std::vector< std::vector >& out) { 1258 | if (!isSet) { 1259 | if (!defaults.empty()) { 1260 | std::vector< std::string > strings; 1261 | SplitDelim(defaults, delim, strings); 1262 | if (out.size() < 1) out.resize(1); 1263 | StringsToFloats(strings, out[0]); 1264 | } 1265 | } else { 1266 | if (!args.empty()) { 1267 | int n = args.size(); 1268 | if ((long int)out.size() < n) out.resize(n); 1269 | for(int i=0; i < n; ++i) { 1270 | StringsToFloats(args[i], &out[i]); 1271 | } 1272 | } 1273 | } 1274 | }; 1275 | /* ################################################################### */ 1276 | void OptionGroup::getMultiDoubles(std::vector< std::vector >& out) { 1277 | if (!isSet) { 1278 | if (!defaults.empty()) { 1279 | std::vector< std::string > strings; 1280 | SplitDelim(defaults, delim, strings); 1281 | if (out.size() < 1) out.resize(1); 1282 | StringsToDoubles(strings, out[0]); 1283 | } 1284 | } else { 1285 | if (!args.empty()) { 1286 | int n = args.size(); 1287 | if ((long int)out.size() < n) out.resize(n); 1288 | for(int i=0; i < n; ++i) { 1289 | StringsToDoubles(args[i], &out[i]); 1290 | } 1291 | } 1292 | } 1293 | }; 1294 | /* ################################################################### */ 1295 | void OptionGroup::getMultiStrings(std::vector< std::vector >& out) { 1296 | if (!isSet) { 1297 | if (!defaults.empty()) { 1298 | std::vector< std::string > strings; 1299 | SplitDelim(defaults, delim, strings); 1300 | if (out.size() < 1) out.resize(1); 1301 | out[0] = strings; 1302 | } 1303 | } else { 1304 | if (!args.empty()) { 1305 | int n = args.size(); 1306 | if ((long int)out.size() < n) out.resize(n); 1307 | 1308 | for(int i=0; i < n; ++i) { 1309 | for(int j=0; j < (long int)args[i]->size(); ++j) 1310 | out[i].push_back( *args[i]->at(j) ); 1311 | } 1312 | } 1313 | } 1314 | }; 1315 | /* ################################################################### */ 1316 | typedef std::map< int, ezOptionValidator* > ValidatorMap; 1317 | 1318 | class ezOptionParser { 1319 | public: 1320 | // How to layout usage descriptions with the option flags. 1321 | enum Layout { ALIGN, INTERLEAVE, STAGGER }; 1322 | 1323 | inline ~ezOptionParser(); 1324 | 1325 | inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, ezOptionValidator* validator=0); 1326 | inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, ezOptionValidator* validator=0); 1327 | inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, ezOptionValidator* validator=0); 1328 | inline void add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, const char * flag4, ezOptionValidator* validator=0); 1329 | inline bool exportFile(const char * filename, bool all=false); 1330 | inline OptionGroup * get(const char * name); 1331 | inline void getUsage(std::string & usage, int width=80, Layout layout=ALIGN); 1332 | inline void getUsageDescriptions(std::string & usage, int width=80, Layout layout=STAGGER); 1333 | inline bool gotExpected(std::vector & badOptions); 1334 | inline bool gotRequired(std::vector & badOptions); 1335 | inline bool gotValid(std::vector & badOptions, std::vector & badArgs); 1336 | inline bool importFile(const char * filename, char comment='#'); 1337 | inline int isSet(const char * name); 1338 | inline int isSet(std::string & name); 1339 | inline void parse(int argc, const char * argv[]); 1340 | inline void prettyPrint(std::string & out); 1341 | inline void reset(); 1342 | inline void resetArgs(); 1343 | 1344 | // Insert extra empty line betwee each option's usage description. 1345 | char doublespace; 1346 | // General description in human language on what the user's tool does. 1347 | // It's the first section to get printed in the full usage message. 1348 | std::string overview; 1349 | // A synopsis of command and options usage to show expected order of input arguments. 1350 | // It's the second section to get printed in the full usage message. 1351 | std::string syntax; 1352 | // Example (third) section in usage message. 1353 | std::string example; 1354 | // Final section printed in usage message. For contact, copyrights, version info. 1355 | std::string footer; 1356 | // Map from an option to an Id of its parent group. 1357 | std::map< std::string, int > optionGroupIds; 1358 | // Unordered collection of the option groups. 1359 | std::vector< OptionGroup* > groups; 1360 | // Store unexpected args in input. 1361 | std::vector< std::string* > unknownArgs; 1362 | // List of args that occur left-most before first option flag. 1363 | std::vector< std::string* > firstArgs; 1364 | // List of args that occur after last right-most option flag and its args. 1365 | std::vector< std::string* > lastArgs; 1366 | // List of validators. 1367 | ValidatorMap validators; 1368 | // Maps group id to a validator index into vector of validators. Validator index is -1 if there is no validator for group. 1369 | std::map< int, int > groupValidators; 1370 | }; 1371 | /* ################################################################### */ 1372 | ezOptionParser::~ezOptionParser() { 1373 | reset(); 1374 | } 1375 | /* ################################################################### */ 1376 | void ezOptionParser::reset() { 1377 | this->doublespace = 1; 1378 | 1379 | int i; 1380 | for(i=0; i < (long int)groups.size(); ++i) 1381 | delete groups[i]; 1382 | groups.clear(); 1383 | 1384 | for(i=0; i < (long int)unknownArgs.size(); ++i) 1385 | delete unknownArgs[i]; 1386 | unknownArgs.clear(); 1387 | 1388 | for(i=0; i < (long int)firstArgs.size(); ++i) 1389 | delete firstArgs[i]; 1390 | firstArgs.clear(); 1391 | 1392 | for(i=0; i < (long int)lastArgs.size(); ++i) 1393 | delete lastArgs[i]; 1394 | lastArgs.clear(); 1395 | 1396 | ValidatorMap::iterator it; 1397 | for(it = validators.begin(); it != validators.end(); ++it) 1398 | delete it->second; 1399 | 1400 | validators.clear(); 1401 | optionGroupIds.clear(); 1402 | groupValidators.clear(); 1403 | }; 1404 | /* ################################################################### */ 1405 | void ezOptionParser::resetArgs() { 1406 | int i; 1407 | for(i=0; i < (long int)groups.size(); ++i) 1408 | groups[i]->clearArgs(); 1409 | 1410 | for(i=0; i < (long int)unknownArgs.size(); ++i) 1411 | delete unknownArgs[i]; 1412 | unknownArgs.clear(); 1413 | 1414 | for(i=0; i < (long int)firstArgs.size(); ++i) 1415 | delete firstArgs[i]; 1416 | firstArgs.clear(); 1417 | 1418 | for(i=0; i < (long int)lastArgs.size(); ++i) 1419 | delete lastArgs[i]; 1420 | lastArgs.clear(); 1421 | }; 1422 | /* ################################################################### */ 1423 | void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, ezOptionValidator* validator) { 1424 | int id = this->groups.size(); 1425 | OptionGroup * g = new OptionGroup; 1426 | g->defaults = defaults; 1427 | g->isRequired = required; 1428 | g->expectArgs = expectArgs; 1429 | g->delim = delim; 1430 | g->isSet = 0; 1431 | g->help = help; 1432 | std::string *f1 = new std::string(flag1); 1433 | g->flags.push_back( f1 ); 1434 | this->optionGroupIds[flag1] = id; 1435 | this->groups.push_back(g); 1436 | 1437 | if (validator) { 1438 | int vid = validator->id; 1439 | validators[vid] = validator; 1440 | groupValidators[id] = vid; 1441 | } else { 1442 | groupValidators[id] = -1; 1443 | } 1444 | }; 1445 | /* ################################################################### */ 1446 | void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, ezOptionValidator* validator) { 1447 | int id = this->groups.size(); 1448 | OptionGroup * g = new OptionGroup; 1449 | g->defaults = defaults; 1450 | g->isRequired = required; 1451 | g->expectArgs = expectArgs; 1452 | g->delim = delim; 1453 | g->isSet = 0; 1454 | g->help = help; 1455 | std::string *f1 = new std::string(flag1); 1456 | g->flags.push_back( f1 ); 1457 | std::string *f2 = new std::string(flag2); 1458 | g->flags.push_back( f2 ); 1459 | this->optionGroupIds[flag1] = id; 1460 | this->optionGroupIds[flag2] = id; 1461 | 1462 | this->groups.push_back(g); 1463 | 1464 | if (validator) { 1465 | int vid = validator->id; 1466 | validators[vid] = validator; 1467 | groupValidators[id] = vid; 1468 | } else { 1469 | groupValidators[id] = -1; 1470 | } 1471 | }; 1472 | /* ################################################################### */ 1473 | void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, ezOptionValidator* validator) { 1474 | int id = this->groups.size(); 1475 | OptionGroup * g = new OptionGroup; 1476 | g->defaults = defaults; 1477 | g->isRequired = required; 1478 | g->expectArgs = expectArgs; 1479 | g->delim = delim; 1480 | g->isSet = 0; 1481 | g->help = help; 1482 | std::string *f1 = new std::string(flag1); 1483 | g->flags.push_back( f1 ); 1484 | std::string *f2 = new std::string(flag2); 1485 | g->flags.push_back( f2 ); 1486 | std::string *f3 = new std::string(flag3); 1487 | g->flags.push_back( f3 ); 1488 | this->optionGroupIds[flag1] = id; 1489 | this->optionGroupIds[flag2] = id; 1490 | this->optionGroupIds[flag3] = id; 1491 | 1492 | this->groups.push_back(g); 1493 | 1494 | if (validator) { 1495 | int vid = validator->id; 1496 | validators[vid] = validator; 1497 | groupValidators[id] = vid; 1498 | } else { 1499 | groupValidators[id] = -1; 1500 | } 1501 | }; 1502 | /* ################################################################### */ 1503 | void ezOptionParser::add(const char * defaults, bool required, int expectArgs, char delim, const char * help, const char * flag1, const char * flag2, const char * flag3, const char * flag4, ezOptionValidator* validator) { 1504 | int id = this->groups.size(); 1505 | OptionGroup * g = new OptionGroup; 1506 | g->defaults = defaults; 1507 | g->isRequired = required; 1508 | g->expectArgs = expectArgs; 1509 | g->delim = delim; 1510 | g->isSet = 0; 1511 | g->help = help; 1512 | std::string *f1 = new std::string(flag1); 1513 | g->flags.push_back( f1 ); 1514 | std::string *f2 = new std::string(flag2); 1515 | g->flags.push_back( f2 ); 1516 | std::string *f3 = new std::string(flag3); 1517 | g->flags.push_back( f3 ); 1518 | std::string *f4 = new std::string(flag4); 1519 | g->flags.push_back( f4 ); 1520 | this->optionGroupIds[flag1] = id; 1521 | this->optionGroupIds[flag2] = id; 1522 | this->optionGroupIds[flag3] = id; 1523 | this->optionGroupIds[flag4] = id; 1524 | 1525 | this->groups.push_back(g); 1526 | 1527 | if (validator) { 1528 | int vid = validator->id; 1529 | validators[vid] = validator; 1530 | groupValidators[id] = vid; 1531 | } else { 1532 | groupValidators[id] = -1; 1533 | } 1534 | }; 1535 | /* ################################################################### */ 1536 | bool ezOptionParser::exportFile(const char * filename, bool all) { 1537 | int i; 1538 | std::string out; 1539 | bool quote; 1540 | 1541 | // Export the first args, except the program name, so start from 1. 1542 | for(i=1; i < (long int)firstArgs.size(); ++i) { 1543 | quote = ((firstArgs[i]->find_first_of(" \t") != std::string::npos) && (firstArgs[i]->find_first_of("\'\"") == std::string::npos)); 1544 | 1545 | if (quote) 1546 | out.append("\""); 1547 | 1548 | out.append(*firstArgs[i]); 1549 | if (quote) 1550 | out.append("\""); 1551 | 1552 | out.append(" "); 1553 | } 1554 | 1555 | if (firstArgs.size() > 1) 1556 | out.append("\n"); 1557 | 1558 | std::vector stringPtrs(groups.size()); 1559 | int m; 1560 | int n = groups.size(); 1561 | for(i=0; i < n; ++i) { 1562 | stringPtrs[i] = groups[i]->flags[0]; 1563 | } 1564 | 1565 | OptionGroup *g; 1566 | // Sort first flag of each group with other groups. 1567 | std::sort(stringPtrs.begin(), stringPtrs.end(), CmpOptStringPtr); 1568 | for(i=0; i < n; ++i) { 1569 | g = get(stringPtrs[i]->c_str()); 1570 | if (g->isSet || all) { 1571 | if (!g->isSet || g->args.empty()) { 1572 | if (!g->defaults.empty()) { 1573 | out.append(*stringPtrs[i]); 1574 | out.append(" "); 1575 | quote = ((g->defaults.find_first_of(" \t") != std::string::npos) && (g->defaults.find_first_of("\'\"") == std::string::npos)); 1576 | if (quote) 1577 | out.append("\""); 1578 | 1579 | out.append(g->defaults); 1580 | if (quote) 1581 | out.append("\""); 1582 | 1583 | out.append("\n"); 1584 | } 1585 | } else { 1586 | int na = g->args.size(); 1587 | for(int j=0; j < na; ++j) { 1588 | out.append(*stringPtrs[i]); 1589 | out.append(" "); 1590 | m = g->args[j]->size(); 1591 | 1592 | for(int k=0; k < m; ++k) { 1593 | quote = ( (*g->args[j]->at(k)).find_first_of(" \t") != std::string::npos ); 1594 | if (quote) 1595 | out.append("\""); 1596 | 1597 | out.append(*g->args[j]->at(k)); 1598 | if (quote) 1599 | out.append("\""); 1600 | 1601 | if ((g->delim) && ((k+1) != m)) 1602 | out.append(1,g->delim); 1603 | } 1604 | out.append("\n"); 1605 | } 1606 | } 1607 | } 1608 | } 1609 | 1610 | // Export the last args. 1611 | for(i=0; i < (long int)lastArgs.size(); ++i) { 1612 | quote = ( lastArgs[i]->find_first_of(" \t") != std::string::npos ); 1613 | if (quote) 1614 | out.append("\""); 1615 | 1616 | out.append(*lastArgs[i]); 1617 | if (quote) 1618 | out.append("\""); 1619 | 1620 | out.append(" "); 1621 | } 1622 | 1623 | std::ofstream file(filename); 1624 | if (!file.is_open()) 1625 | return false; 1626 | 1627 | file << out; 1628 | file.close(); 1629 | 1630 | return true; 1631 | }; 1632 | /* ################################################################### */ 1633 | // Does not overwrite current options. 1634 | // Returns true if file was read successfully. 1635 | // So if this is used before parsing CLI, then option values will reflect 1636 | // this file, but if used after parsing CLI, then values will contain 1637 | // both CLI values and file's values. 1638 | // 1639 | // Comment lines are allowed if prefixed with #. 1640 | // Strings should be quoted as usual. 1641 | bool ezOptionParser::importFile(const char * filename, char comment) { 1642 | std::ifstream file (filename, std::ios::in | std::ios::ate); 1643 | if (!file.is_open()) 1644 | return false; 1645 | 1646 | // Read entire file contents. 1647 | std::ifstream::pos_type size = file.tellg(); 1648 | char * memblock = new char[(int)size+1]; // Add one for end of string. 1649 | file.seekg (0, std::ios::beg); 1650 | file.read (memblock, size); 1651 | memblock[size] = '\0'; 1652 | file.close(); 1653 | 1654 | // Find comment lines. 1655 | std::list lines; 1656 | std::string memblockstring(memblock); 1657 | delete[] memblock; 1658 | SplitDelim(memblockstring, '\n', lines); 1659 | int i,j,n; 1660 | std::list::iterator iter; 1661 | std::vector sq, dq; // Single and double quote indices. 1662 | std::vector::iterator lo; // For searching quote indices. 1663 | size_t pos; 1664 | const char *str; 1665 | std::string *line; 1666 | // Find all single and double quotes to correctly handle comment tokens. 1667 | for(iter=lines.begin(); iter != lines.end(); ++iter) { 1668 | line = *iter; 1669 | str = line->c_str(); 1670 | n = line->size(); 1671 | sq.clear(); 1672 | dq.clear(); 1673 | if (n) { 1674 | // If first char is comment, then erase line and continue. 1675 | pos = line->find_first_not_of(" \t\r"); 1676 | if ((pos==std::string::npos) || (line->at(pos)==comment)) { 1677 | line->erase(); 1678 | continue; 1679 | } else { 1680 | // Erase whitespace prefix. 1681 | line->erase(0,pos); 1682 | n = line->size(); 1683 | } 1684 | 1685 | if (line->at(0)=='"') 1686 | dq.push_back(0); 1687 | 1688 | if (line->at(0)=='\'') 1689 | sq.push_back(0); 1690 | } else { // Empty line. 1691 | continue; 1692 | } 1693 | 1694 | for(i=1; i < n; ++i) { 1695 | if ( (str[i]=='"') && (str[i-1]!='\\') ) 1696 | dq.push_back(i); 1697 | else if ( (str[i]=='\'') && (str[i-1]!='\\') ) 1698 | sq.push_back(i); 1699 | } 1700 | // Scan for comments, and when found, check bounds of quotes. 1701 | // Start with second char because already checked first char. 1702 | for(i=1; i < n; ++i) { 1703 | if ( (line->at(i)==comment) && (line->at(i-1)!='\\') ) { 1704 | // If within open/close quote pair, then not real comment. 1705 | if (sq.size()) { 1706 | lo = std::lower_bound(sq.begin(), sq.end(), i); 1707 | // All start of strings will be even indices, closing quotes is odd indices. 1708 | j = (int)(lo-sq.begin()); 1709 | if ( (j % 2) == 0) { // Even implies comment char not in quote pair. 1710 | // Erase from comment char to end of line. 1711 | line->erase(i); 1712 | break; 1713 | } 1714 | } else if (dq.size()) { 1715 | // Repeat tests for double quotes. 1716 | lo = std::lower_bound(dq.begin(), dq.end(), i); 1717 | j = (int)(lo-dq.begin()); 1718 | if ( (j % 2) == 0) { 1719 | line->erase(i); 1720 | break; 1721 | } 1722 | } else { 1723 | // Not in quotes. 1724 | line->erase(i); 1725 | break; 1726 | } 1727 | } 1728 | } 1729 | } 1730 | 1731 | std::string cmd; 1732 | // Convert list to string without newlines to simulate commandline. 1733 | for(iter=lines.begin(); iter != lines.end(); ++iter) { 1734 | if (! (*iter)->empty()) { 1735 | cmd.append(**iter); 1736 | cmd.append(" "); 1737 | } 1738 | } 1739 | 1740 | // Now parse as if from command line. 1741 | int argc=0; 1742 | char** argv = CommandLineToArgvA((char*)cmd.c_str(), &argc); 1743 | 1744 | // Parse. 1745 | parse(argc, (const char**)argv); 1746 | if (argv) free(argv); 1747 | for(iter=lines.begin(); iter != lines.end(); ++iter) 1748 | delete *iter; 1749 | 1750 | return true; 1751 | }; 1752 | /* ################################################################### */ 1753 | int ezOptionParser::isSet(const char * name) { 1754 | std::string sname(name); 1755 | 1756 | if (this->optionGroupIds.count(sname)) { 1757 | return this->groups[this->optionGroupIds[sname]]->isSet; 1758 | } 1759 | 1760 | return 0; 1761 | }; 1762 | /* ################################################################### */ 1763 | int ezOptionParser::isSet(std::string & name) { 1764 | if (this->optionGroupIds.count(name)) { 1765 | return this->groups[this->optionGroupIds[name]]->isSet; 1766 | } 1767 | 1768 | return 0; 1769 | }; 1770 | /* ################################################################### */ 1771 | OptionGroup * ezOptionParser::get(const char * name) { 1772 | if (optionGroupIds.count(name)) { 1773 | return groups[optionGroupIds[name]]; 1774 | } 1775 | 1776 | return 0; 1777 | }; 1778 | /* ################################################################### */ 1779 | void ezOptionParser::getUsage(std::string & usage, int width, Layout layout) { 1780 | 1781 | usage.append(overview); 1782 | usage.append("\n\nUSAGE:\n"); 1783 | usage.append(syntax); 1784 | usage.append("\n\nOPTIONS:\n"); 1785 | getUsageDescriptions(usage, width, layout); 1786 | 1787 | if (!example.empty()) { 1788 | usage.append("\nEXAMPLES:\n"); 1789 | usage.append(example); 1790 | } 1791 | 1792 | if (!footer.empty()) { 1793 | usage.append(footer); 1794 | } 1795 | }; 1796 | /* ################################################################### */ 1797 | // Creates 2 column formatted help descriptions for each option flag. 1798 | void ezOptionParser::getUsageDescriptions(std::string & usage, int width, Layout layout) { 1799 | // Sort each flag list amongst each group. 1800 | int i; 1801 | // Store index of flag groups before sort for easy lookup later. 1802 | std::map stringPtrToIndexMap; 1803 | std::vector stringPtrs(groups.size()); 1804 | 1805 | for(i=0; i < (long int)groups.size(); ++i) { 1806 | std::sort(groups[i]->flags.begin(), groups[i]->flags.end(), CmpOptStringPtr); 1807 | stringPtrToIndexMap[groups[i]->flags[0]] = i; 1808 | stringPtrs[i] = groups[i]->flags[0]; 1809 | } 1810 | 1811 | size_t j, k; 1812 | std::string opts; 1813 | std::vector sortedOpts; 1814 | // Sort first flag of each group with other groups. 1815 | std::sort(stringPtrs.begin(), stringPtrs.end(), CmpOptStringPtr); 1816 | for(i=0; i < (long int)groups.size(); ++i) { 1817 | k = stringPtrToIndexMap[stringPtrs[i]]; 1818 | opts.clear(); 1819 | for(j=0; j < groups[k]->flags.size()-1; ++j) { 1820 | opts.append(*groups[k]->flags[j]); 1821 | opts.append(", "); 1822 | 1823 | if ((long int)opts.size() > width) 1824 | opts.append("\n"); 1825 | } 1826 | // The last flag. No need to append comma anymore. 1827 | opts.append( *groups[k]->flags[j] ); 1828 | 1829 | if (groups[k]->expectArgs) { 1830 | opts.append(" ARG"); 1831 | 1832 | if (groups[k]->delim) { 1833 | opts.append("1["); 1834 | opts.append(1, groups[k]->delim); 1835 | opts.append("ARGn]"); 1836 | } 1837 | } 1838 | 1839 | sortedOpts.push_back(opts); 1840 | } 1841 | 1842 | // Each option group will use this to build multiline help description. 1843 | std::list desc; 1844 | // Number of whitespaces from start of line to description (interleave layout) or 1845 | // gap between flag names and description (align, stagger layouts). 1846 | int gutter = 3; 1847 | 1848 | // Find longest opt flag string to set column start for help usage descriptions. 1849 | int maxlen=0; 1850 | if (layout == ALIGN) { 1851 | for(i=0; i < (long int)groups.size(); ++i) { 1852 | if (maxlen < (long int)sortedOpts[i].size()) 1853 | maxlen = sortedOpts[i].size(); 1854 | } 1855 | } 1856 | 1857 | // The amount of space remaining on a line for help text after flags. 1858 | int helpwidth; 1859 | std::list::iterator cIter, insertionIter; 1860 | size_t pos; 1861 | for(i=0; i < (long int)groups.size(); ++i) { 1862 | k = stringPtrToIndexMap[stringPtrs[i]]; 1863 | 1864 | if (layout == STAGGER) 1865 | maxlen = sortedOpts[i].size(); 1866 | 1867 | int pad = gutter + maxlen; 1868 | helpwidth = width - pad; 1869 | 1870 | // All the following split-fu could be optimized by just using substring (offset, length) tuples, but just to get it done, we'll do some not-too expensive string copying. 1871 | SplitDelim(groups[k]->help, '\n', desc); 1872 | // Split lines longer than allowable help width. 1873 | for(insertionIter=desc.begin(), cIter=insertionIter++; 1874 | cIter != desc.end(); 1875 | cIter=insertionIter++) { 1876 | if ((long int)((*cIter)->size()) > helpwidth) { 1877 | // Get pointer to next string to insert new strings before it. 1878 | std::string *rem = *cIter; 1879 | // Remove this line and add back in pieces. 1880 | desc.erase(cIter); 1881 | // Loop until remaining string is short enough. 1882 | while ((long int)rem->size() > helpwidth) { 1883 | // Find whitespace to split before helpwidth. 1884 | if (rem->at(helpwidth) == ' ') { 1885 | // If word ends exactly at helpwidth, then split after it. 1886 | pos = helpwidth; 1887 | } else { 1888 | // Otherwise, split occurs midword, so find whitespace before this word. 1889 | pos = rem->rfind(" ", helpwidth); 1890 | } 1891 | // Insert split string. 1892 | desc.insert(insertionIter, new std::string(*rem, 0, pos)); 1893 | // Now skip any whitespace to start new line. 1894 | pos = rem->find_first_not_of(' ', pos); 1895 | rem->erase(0, pos); 1896 | } 1897 | 1898 | if (rem->size()) 1899 | desc.insert(insertionIter, rem); 1900 | else 1901 | delete rem; 1902 | } 1903 | } 1904 | 1905 | usage.append(sortedOpts[i]); 1906 | if (layout != INTERLEAVE) 1907 | // Add whitespace between option names and description. 1908 | usage.append(pad - sortedOpts[i].size(), ' '); 1909 | else { 1910 | usage.append("\n"); 1911 | usage.append(gutter, ' '); 1912 | } 1913 | 1914 | if (desc.size() > 0) { // Crash fix by Bruce Shankle. 1915 | // First line already padded above (before calling SplitDelim) after option flag names. 1916 | cIter = desc.begin(); 1917 | usage.append(**cIter); 1918 | usage.append("\n"); 1919 | // Now inject the pad for each line. 1920 | for(++cIter; cIter != desc.end(); ++cIter) { 1921 | usage.append(pad, ' '); 1922 | usage.append(**cIter); 1923 | usage.append("\n"); 1924 | } 1925 | 1926 | if (this->doublespace) usage.append("\n"); 1927 | 1928 | for(cIter=desc.begin(); cIter != desc.end(); ++cIter) 1929 | delete *cIter; 1930 | 1931 | desc.clear(); 1932 | } 1933 | 1934 | } 1935 | }; 1936 | /* ################################################################### */ 1937 | bool ezOptionParser::gotExpected(std::vector & badOptions) { 1938 | int i,j; 1939 | 1940 | for(i=0; i < (long int)groups.size(); ++i) { 1941 | OptionGroup *g = groups[i]; 1942 | // If was set, ensure number of args is correct. 1943 | if (g->isSet) { 1944 | if ((g->expectArgs != 0) && g->args.empty()) { 1945 | badOptions.push_back(*g->flags[0]); 1946 | continue; 1947 | } 1948 | 1949 | for(j=0; j < (long int)g->args.size(); ++j) { 1950 | if ((g->expectArgs != -1) && (g->expectArgs != (long int)g->args[j]->size())) 1951 | badOptions.push_back(*g->flags[0]); 1952 | } 1953 | } 1954 | } 1955 | 1956 | return badOptions.empty(); 1957 | }; 1958 | /* ################################################################### */ 1959 | bool ezOptionParser::gotRequired(std::vector & badOptions) { 1960 | int i; 1961 | 1962 | for(i=0; i < (long int)groups.size(); ++i) { 1963 | OptionGroup *g = groups[i]; 1964 | // Simple case when required but user never set it. 1965 | if (g->isRequired && (!g->isSet)) { 1966 | badOptions.push_back(*g->flags[0]); 1967 | continue; 1968 | } 1969 | } 1970 | 1971 | return badOptions.empty(); 1972 | }; 1973 | /* ################################################################### */ 1974 | bool ezOptionParser::gotValid(std::vector & badOptions, std::vector & badArgs) { 1975 | int groupid, validatorid; 1976 | std::map< int, int >::iterator it; 1977 | 1978 | for(it = groupValidators.begin(); it != groupValidators.end(); ++it) { 1979 | groupid = it->first; 1980 | validatorid = it->second; 1981 | if (validatorid < 0) continue; 1982 | 1983 | OptionGroup *g = groups[groupid]; 1984 | ezOptionValidator *v = validators[validatorid]; 1985 | bool nextgroup = false; 1986 | 1987 | for (int i = 0; i < (long int)g->args.size(); ++i) { 1988 | if (nextgroup) break; 1989 | std::vector< std::string* > * args = g->args[i]; 1990 | for (int j = 0; j < (long int)args->size(); ++j) { 1991 | if (!v->isValid(args->at(j))) { 1992 | badOptions.push_back(*g->flags[0]); 1993 | badArgs.push_back(*args->at(j)); 1994 | nextgroup = true; 1995 | break; 1996 | } 1997 | } 1998 | } 1999 | } 2000 | 2001 | return badOptions.empty(); 2002 | }; 2003 | /* ################################################################### */ 2004 | void ezOptionParser::parse(int argc, const char * argv[]) { 2005 | if (argc < 1) return; 2006 | 2007 | int i, k, firstOptIndex=0, lastOptIndex=0; 2008 | std::string s; 2009 | OptionGroup *g; 2010 | 2011 | for(i=0; i < argc; ++i) { 2012 | s = argv[i]; 2013 | 2014 | if (optionGroupIds.count(s)) 2015 | break; 2016 | } 2017 | 2018 | firstOptIndex = i; 2019 | 2020 | if (firstOptIndex == argc) { 2021 | // No flags encountered, so set last args. 2022 | this->firstArgs.push_back(new std::string(argv[0])); 2023 | 2024 | for(k=1; k < argc; ++k) 2025 | this->lastArgs.push_back(new std::string(argv[k])); 2026 | 2027 | return; 2028 | } 2029 | 2030 | // Store initial args before opts appear. 2031 | for(k=0; k < i; ++k) { 2032 | this->firstArgs.push_back(new std::string(argv[k])); 2033 | } 2034 | 2035 | for(; i < argc; ++i) { 2036 | s = argv[i]; 2037 | 2038 | if (optionGroupIds.count(s)) { 2039 | k = optionGroupIds[s]; 2040 | g = groups[k]; 2041 | g->isSet = 1; 2042 | g->parseIndex.push_back(i); 2043 | 2044 | if (g->expectArgs) { 2045 | // Read ahead to get args. 2046 | ++i; 2047 | if (i >= argc) return; 2048 | g->args.push_back(new std::vector); 2049 | SplitDelim(argv[i], g->delim, g->args.back()); 2050 | } 2051 | lastOptIndex = i; 2052 | } 2053 | } 2054 | 2055 | // Scan for unknown opts/arguments. 2056 | for(i=firstOptIndex; i <= lastOptIndex; ++i) { 2057 | s = argv[i]; 2058 | 2059 | if (optionGroupIds.count(s)) { 2060 | k = optionGroupIds[s]; 2061 | g = groups[k]; 2062 | if (g->expectArgs) { 2063 | // Read ahead for args and skip them. 2064 | ++i; 2065 | } 2066 | } else { 2067 | unknownArgs.push_back(new std::string(argv[i])); 2068 | } 2069 | } 2070 | 2071 | if ( lastOptIndex >= (argc-1) ) return; 2072 | 2073 | // Store final args without flags. 2074 | for(k=lastOptIndex + 1; k < argc; ++k) { 2075 | this->lastArgs.push_back(new std::string(argv[k])); 2076 | } 2077 | }; 2078 | /* ################################################################### */ 2079 | void ezOptionParser::prettyPrint(std::string & out) { 2080 | const size_t BUF_SIZE = 1024; 2081 | char tmp[BUF_SIZE]; 2082 | int i,j,k; 2083 | 2084 | out += "First Args:\n"; 2085 | for(i=0; i < (long int)firstArgs.size(); ++i) { 2086 | snprintf(tmp, BUF_SIZE, "%d: %s\n", i+1, firstArgs[i]->c_str()); 2087 | out += tmp; 2088 | } 2089 | 2090 | // Sort the option flag names. 2091 | int n = groups.size(); 2092 | std::vector stringPtrs(n); 2093 | for(i=0; i < n; ++i) { 2094 | stringPtrs[i] = groups[i]->flags[0]; 2095 | } 2096 | 2097 | // Sort first flag of each group with other groups. 2098 | std::sort(stringPtrs.begin(), stringPtrs.end(), CmpOptStringPtr); 2099 | 2100 | out += "\nOptions:\n"; 2101 | OptionGroup *g; 2102 | for(i=0; i < n; ++i) { 2103 | g = get(stringPtrs[i]->c_str()); 2104 | out += "\n"; 2105 | // The flag names: 2106 | for(j=0; j < (long int)g->flags.size()-1; ++j) { 2107 | snprintf(tmp, BUF_SIZE, "%s, ", g->flags[j]->c_str()); 2108 | out += tmp; 2109 | } 2110 | snprintf(tmp, BUF_SIZE, "%s:\n", g->flags.back()->c_str()); 2111 | out += tmp; 2112 | 2113 | if (g->isSet) { 2114 | if (g->expectArgs) { 2115 | if (g->args.empty()) { 2116 | snprintf(tmp, BUF_SIZE, "%s (default)\n", g->defaults.c_str()); 2117 | out += tmp; 2118 | } else { 2119 | for(k=0; k < (long int)g->args.size(); ++k) { 2120 | for(j=0; j < (long int)g->args[k]->size()-1; ++j) { 2121 | snprintf(tmp, BUF_SIZE, "%s%c", g->args[k]->at(j)->c_str(), g->delim); 2122 | out += tmp; 2123 | } 2124 | snprintf(tmp, BUF_SIZE, "%s\n", g->args[k]->back()->c_str()); 2125 | out += tmp; 2126 | } 2127 | } 2128 | } else { // Set but no args expected. 2129 | snprintf(tmp, BUF_SIZE, "Set\n"); 2130 | out += tmp; 2131 | } 2132 | } else { 2133 | snprintf(tmp, BUF_SIZE, "Not set\n"); 2134 | out += tmp; 2135 | } 2136 | } 2137 | 2138 | out += "\nLast Args:\n"; 2139 | for(i=0; i < (long int)lastArgs.size(); ++i) { 2140 | snprintf(tmp, BUF_SIZE, "%d: %s\n", i+1, lastArgs[i]->c_str()); 2141 | out += tmp; 2142 | } 2143 | 2144 | out += "\nUnknown Args:\n"; 2145 | for(i=0; i < (long int)unknownArgs.size(); ++i) { 2146 | snprintf(tmp, BUF_SIZE, "%d: %s\n", i+1, unknownArgs[i]->c_str()); 2147 | out += tmp; 2148 | } 2149 | }; 2150 | } 2151 | /* ################################################################### */ 2152 | #endif /* EZ_OPTION_PARSER_H */ 2153 | -------------------------------------------------------------------------------- /src/sfcRom.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "sfcRom.hpp" 11 | 12 | using namespace std; 13 | 14 | bool validResetOpcode(uint8_t op); 15 | bool validInterruptOpcode(uint8_t op); 16 | string sjisToString(uint8_t code); 17 | uint16_t getWord(const vector& vec, size_t offset); 18 | void putWord(vector& vec, size_t offset, uint16_t value); 19 | 20 | sfcRom::sfcRom(const string& path) 21 | : filepath(path) { 22 | 23 | int issues = 0; 24 | 25 | // Read file into buffer 26 | image = vector(0); 27 | { 28 | ifstream file(path, ios::binary | ios::ate); 29 | if (file) { 30 | size_t fileSize = file.tellg(); 31 | if ((fileSize & 0x3ff) == 0x200) { 32 | hasCopierHeader = true; 33 | imageOffset = 0x200; 34 | ++issues; 35 | } 36 | 37 | imageSize = fileSize - imageOffset; 38 | if (imageSize < 0x8000 || imageSize > 0xc00000 || imageSize % 0x8000 != 0) { return; } 39 | 40 | file.seekg(imageOffset, ios::beg); 41 | image.resize(imageSize); 42 | file.read((char*)&image[0], imageSize); 43 | } else { 44 | return; 45 | } 46 | } 47 | 48 | // Review possible header locations and pick best match 49 | { 50 | vector possibleHeaderLocations = {0x7fb0, 0xffb0, 0x40ffb0}; 51 | vector> scoredLocations = {}; 52 | 53 | for (size_t loc : possibleHeaderLocations) { 54 | int score = scoreHeaderLocation(loc); 55 | if (score > -2) { scoredLocations.emplace_back(score, loc); } 56 | } 57 | int topScore = INT_MIN; 58 | for (auto scoredLocation : scoredLocations) { 59 | if (scoredLocation.first >= topScore) { 60 | headerLocation = scoredLocation.second; 61 | topScore = scoredLocation.first; 62 | } 63 | } 64 | 65 | if (headerLocation == 0) { return; } 66 | isValid = true; 67 | } 68 | 69 | // We're probably dealing with an SFC ROM image 70 | getHeaderInfo(vector(image.begin() + headerLocation, image.begin() + headerLocation + 0x50)); 71 | 72 | // Check title 73 | { 74 | hasCorrectTitle = true; 75 | for (size_t i = 0; i < 21; ++i) { 76 | if (sjisToString(image[headerLocation + 0x10 + i]).empty()) { hasCorrectTitle = false; } 77 | } 78 | if (!hasCorrectTitle) { 79 | ++issues; 80 | hasSevereIssues = true; 81 | } 82 | } 83 | 84 | // Check ROM make up 85 | { 86 | if ((mode & 0xe0) != 0x20) { 87 | correctedMode = headerLocation >= 0x8000 ? 0x21 : 0x20; 88 | hasSevereIssues = true; 89 | ++issues; 90 | } else { 91 | hasLegalMode = true; 92 | } 93 | 94 | if (mapperName.empty()) { 95 | ++issues; 96 | } else { 97 | hasKnownMapper = true; 98 | } 99 | } 100 | 101 | // Check ROM size 102 | { 103 | if (imageSize > (1 << (romSize + 10)) || imageSize <= (1 << (romSize + 9))) { 104 | uint32_t pot = imageSize; 105 | int potN = 0; 106 | while (pot >>= 1) { 107 | if (pot & 1) { ++potN; } 108 | } 109 | correctedRomSize = (potN > 1) ? 1 : 0; 110 | uint32_t sizeBits = imageSize >> 10; 111 | while (sizeBits >>= 1) { 112 | ++correctedRomSize; 113 | } 114 | if (correctedRomSize != romSize) { ++issues; } 115 | } 116 | } 117 | 118 | // Check RAM size 119 | { 120 | if ((hasRam && ramSize > 0x0f) || (!hasRam && ramSize != 0)) { 121 | ++issues; 122 | } else { 123 | hasCorrectRamSize = true; 124 | } 125 | } 126 | 127 | // Calculate checksum 128 | { 129 | correctedChecksum = calculateChecksum(); 130 | correctedComplement = ~correctedChecksum; 131 | if ((checksum != correctedChecksum) || complement != correctedComplement) { 132 | ++issues; 133 | } else { 134 | hasCorrectChecksum = true; 135 | } 136 | } 137 | 138 | if (issues || hasSevereIssues) { hasIssues = true; } 139 | } 140 | 141 | string sfcRom::description(bool silent) const { 142 | ostringstream os; 143 | if (isValid) { 144 | os << setfill('0') << hex; 145 | 146 | if (!silent) { 147 | os << "ROM info for file \"" << filepath << "\"" 148 | << "\n\n"; 149 | 150 | uint32_t headerAt = headerLocation + (hasNewFormatHeader ? 0 : 0x10); 151 | os << " Header at 0x" << setw(4) << headerAt << '\n'; 152 | os << " Title " << title << '\n'; 153 | if (!gameCode.empty()) { os << " Game code " << gameCode << '\n'; } 154 | os << " Maker code " << makerCode << '\n'; 155 | os << " Country 0x" << setw(2) << static_cast(countryCode) << " (" << country << ")" << '\n'; 156 | os << " Version " << version << '\n'; 157 | os << '\n'; 158 | 159 | int romSizeKb = (int)(1 << romSize); 160 | int imageSizeKb = (int)(imageSize >> 10); 161 | if (romSize > 0x0f || romSize < 0x05) { 162 | os << " ROM size BAD! (Actual size " << dec << imageSizeKb << hex << "KB)" << '\n'; 163 | } else { 164 | os << " ROM size 0x" << setw(2) << static_cast(romSize) << " (" << dec << romSizeKb << hex << "KB"; 165 | if (romSizeKb != imageSizeKb) { 166 | os << ", actual size " << dec << imageSizeKb << hex << "KB)" << '\n'; 167 | } else { 168 | os << ")" << '\n'; 169 | } 170 | } 171 | 172 | if (hasRam) { 173 | if (ramSize > 0x0d) { 174 | os << " RAM size BAD! (0x" << setw(2) << static_cast(ramSize) << ")" << '\n'; 175 | } else { 176 | os << " RAM size 0x" << setw(2) << static_cast(ramSize); 177 | os << " (" << dec << (int)(1 << (ramSize)) << hex << "KB)" << '\n'; 178 | } 179 | } 180 | 181 | if (hasLegalMode) { 182 | os << " Map mode 0x" << setw(2) << static_cast(mapper); 183 | if (mapperName.empty()) { 184 | os << '\n'; 185 | } else { 186 | os << " (" << mapperName << ")" << '\n'; 187 | } 188 | } else { 189 | os << " Map mode BAD! (ROM makeup 0x" << setw(2) << static_cast(mode) << ")" << '\n'; 190 | } 191 | 192 | os << " Chipset 0x" << setw(2) << static_cast(chipset); 193 | if (chipsetSubtype) { os << "/" << setw(2) << static_cast(chipsetSubtype); } 194 | if (chipSetInfo.empty()) { 195 | os << '\n'; 196 | } else { 197 | os << " (" << chipSetInfo << ")" << '\n'; 198 | } 199 | 200 | os << " Speed " << (fast ? "120ns" : "200ns") << '\n'; 201 | os << '\n'; 202 | 203 | os << " Checksum 0x" << setw(4) << checksum << '\n'; 204 | os << " Complement 0x" << setw(4) << complement << '\n'; 205 | 206 | os << '\n'; 207 | } 208 | 209 | if (hasIssues) { 210 | if (silent) { 211 | os << "Issues with \"" << filepath << "\":" << '\n'; 212 | } else { 213 | if (hasSevereIssues) { 214 | os << "Severe issues were found:" << '\n'; 215 | } else { 216 | os << "The following issues were found:" << '\n'; 217 | } 218 | } 219 | 220 | if (hasCorrectTitle == false) { os << " ROM title contains illegal characters" << '\n'; } 221 | if (!hasLegalMode) { 222 | os << " ROM makeup 0x" << setw(2) << static_cast(mode) << " is not allowed"; 223 | os << ", best guess is 0x" << setw(2) << static_cast(correctedMode) << '\n'; 224 | } else if (!hasKnownMapper) { 225 | os << " ROM makeup 0x" << setw(2) << static_cast(mode) << " is an unknown type" << '\n'; 226 | } 227 | if (correctedRomSize && romSize != correctedRomSize) { 228 | os << " ROM size should be 0x" << setw(2) << static_cast(correctedRomSize) << '\n'; 229 | } 230 | if (!hasCorrectRamSize) { 231 | if (hasRam) { 232 | os << " RAM size specified too large" << '\n'; 233 | } else { 234 | os << " RAM size should be 0x00" << '\n'; 235 | } 236 | } 237 | if (!hasCorrectChecksum) { 238 | os << " Checksum/complement should be 0x" << setw(4) << correctedChecksum << "/0x" << setw(4) 239 | << correctedComplement << '\n'; 240 | } 241 | if (hasCopierHeader) { os << " File has a copier header (0x200 bytes)" << '\n'; } 242 | if (!silent) { os << '\n'; } 243 | } 244 | 245 | } else { 246 | os << "File \"" << filepath << "\" is not an SFC ROM image" << '\n'; 247 | } 248 | return os.str(); 249 | } 250 | 251 | string sfcRom::fix(const string& path, bool silent) { 252 | if (!isValid) { return string(); } 253 | 254 | ostringstream os; 255 | 256 | int fixedIssues = 0; 257 | if (checksum != correctedChecksum || complement != correctedComplement) { ++fixedIssues; } 258 | 259 | if (!silent) { os << "Writing ROM image to file \"" << path << "\"" << '\n'; } 260 | 261 | if (hasCopierHeader) { 262 | if (!silent) { os << " Removed copier header" << '\n'; } 263 | } 264 | 265 | if (!hasCorrectTitle) { 266 | // TODO 267 | } 268 | 269 | if (!hasLegalMode) { 270 | image[headerLocation + 0x25] = correctedMode; 271 | ++fixedIssues; 272 | if (!silent) { os << " Fixed ROM makeup" << '\n'; } 273 | } 274 | 275 | if (correctedRomSize && romSize != correctedRomSize) { 276 | image[headerLocation + 0x27] = correctedRomSize; 277 | ++fixedIssues; 278 | if (!silent) { os << " Fixed ROM size" << '\n'; } 279 | } 280 | 281 | if (fixedIssues) { 282 | correctedChecksum = calculateChecksum(); 283 | correctedComplement = ~correctedChecksum; 284 | putWord(image, headerLocation + 0x2c, correctedComplement); 285 | putWord(image, headerLocation + 0x2e, correctedChecksum); 286 | if (!silent) { os << " Fixed checksum" << '\n'; } 287 | } 288 | 289 | if (fixedIssues || hasCopierHeader || path != filepath) { 290 | ofstream file(path, ios::binary | ios::trunc); 291 | if (file && file.good()) { 292 | file.write((char*)&image[0], image.size() * sizeof(uint8_t)); 293 | } else { 294 | ostringstream fail; 295 | fail << "Cannot open file \"" << path << "\" for writing" << '\n'; 296 | return fail.str(); 297 | } 298 | } else { 299 | return string(); 300 | } 301 | 302 | if (!silent) { os << '\n'; } 303 | return os.str(); 304 | } 305 | 306 | int sfcRom::scoreHeaderLocation(size_t loc) const { 307 | if (image.size() < loc + 0x50) { return -100; } 308 | 309 | int score = 0; 310 | vector header = vector(image.begin() + loc, image.begin() + loc + 0x50); 311 | uint16_t reset = getWord(header, 0x4c); 312 | 313 | // If 32K/bank mapper, reset vector must point to upper half 314 | if ((loc & 0xffff) < 0x8000) { 315 | if (reset < 0x8000) { 316 | return -100; 317 | } else { 318 | score += 1; 319 | reset -= 0x8000; 320 | } 321 | } 322 | 323 | // Correct rom makeup byte? 324 | { 325 | uint8_t s_mode = header[0x25]; 326 | uint8_t s_mapper = mode & 0x0f; 327 | if (((s_mode & 0xe0) == 0x20) && 328 | (s_mapper == 0x0 || s_mapper == 0x1 || s_mapper == 0x2 || s_mapper == 0x3 || s_mapper == 0x5 || s_mapper == 0xa)) { 329 | score += 2; 330 | } 331 | } 332 | 333 | // Correct ROM & RAM size bytes? 334 | { 335 | if (header[0x27] >= 0x05 && header[0x27] <= 0x0f) { score += 2; } 336 | if (header[0x28] >= 0x0a) { score += 1; } 337 | } 338 | 339 | // Proper title characters? 340 | { 341 | int validChars = 0; 342 | for (int i = 0; i < 21; ++i) { 343 | if (!sjisToString(header[0x10 + i]).empty()) { ++validChars; } 344 | } 345 | if (validChars == 21) { score += 2; } 346 | } 347 | 348 | // Reasonable reset opcode? 349 | if (validResetOpcode(image[reset])) { 350 | score += 2; 351 | } else { 352 | score -= 4; 353 | } 354 | 355 | return score; 356 | } 357 | 358 | void sfcRom::getHeaderInfo(const vector& header) { 359 | mode = header[0x25]; 360 | mapper = mode & 0x0f; 361 | fast = mode & 0x10; 362 | hasNewFormatHeader = (header[0x2a] == 0x33); 363 | 364 | chipset = header[0x26]; 365 | if (hasNewFormatHeader) { chipsetSubtype = header[0x0f]; } 366 | romSize = header[0x27]; 367 | ramSize = header[0x28]; 368 | countryCode = header[0x29]; 369 | 370 | complement = getWord(header, 0x2c); 371 | checksum = getWord(header, 0x2e); 372 | 373 | title = ""; 374 | for (int i = 0x10; i < 0x10 + 21; ++i) { 375 | title += sjisToString(header[i]); 376 | } 377 | 378 | switch (mapper) { 379 | case 0x0: 380 | mapperName = "LoROM"; 381 | break; 382 | case 0x1: 383 | mapperName = "HiROM"; 384 | break; 385 | case 0x2: 386 | mapperName = "LoROM/S-DD1"; 387 | break; 388 | case 0x3: 389 | mapperName = "LoROM/SA-1"; 390 | break; 391 | case 0x5: 392 | mapperName = "Extended HiROM"; 393 | break; 394 | case 0xa: 395 | mapperName = "Extended HiROM/SPC7110"; 396 | break; 397 | default: 398 | mapperName = string(); 399 | break; 400 | } 401 | 402 | if (hasNewFormatHeader) { 403 | makerCode = string((char*)&header[0x00], 2); 404 | gameCode = string((char*)&header[0x02], 4); 405 | } else { 406 | ostringstream os; 407 | os << "0x" << setfill('0') << setw(2) << hex << static_cast((uint8_t)header[0x2a]); 408 | makerCode = os.str(); 409 | gameCode = string(); 410 | } 411 | 412 | chipSetInfo = string(); 413 | switch (chipset) { 414 | case 0x00: 415 | chipSetInfo = "ROM"; 416 | break; 417 | case 0x01: 418 | chipSetInfo = "ROM, RAM"; 419 | hasRam = true; 420 | break; 421 | case 0x02: 422 | chipSetInfo = "ROM, RAM, Battery"; 423 | hasRam = true; 424 | break; 425 | case 0x03: 426 | chipSetInfo = "ROM, DSP"; 427 | break; 428 | case 0x04: 429 | chipSetInfo = "ROM, RAM, DSP"; 430 | hasRam = true; 431 | break; 432 | case 0x05: 433 | chipSetInfo = "ROM, RAM, DSP, Battery"; 434 | hasRam = true; 435 | break; 436 | case 0x13: 437 | chipSetInfo = "ROM, EXPRAM, MARIO CHIP 1"; 438 | break; 439 | case 0x25: 440 | chipSetInfo = "ROM, RAM, OBC-1, Battery"; 441 | hasRam = true; 442 | break; 443 | case 0x32: 444 | chipSetInfo = "ROM, RAM, SA-1, Battery"; 445 | hasRam = true; 446 | break; 447 | case 0x34: 448 | chipSetInfo = "ROM, RAM, SA-1"; 449 | hasRam = true; 450 | break; 451 | case 0x35: 452 | chipSetInfo = "ROM, RAM, SA-1, Battery"; 453 | hasRam = true; 454 | break; 455 | case 0x36: 456 | chipSetInfo = "ROM, SA-1, Battery"; 457 | break; 458 | case 0x43: 459 | chipSetInfo = "ROM, S-DD1"; 460 | break; 461 | case 0x45: 462 | chipSetInfo = "ROM, RAM, S-DD1, Battery"; 463 | hasRam = true; 464 | break; 465 | case 0x55: 466 | chipSetInfo = "ROM, RAM, S-RTC, Battery"; 467 | hasRam = true; 468 | break; 469 | case 0xe3: 470 | chipSetInfo = "ROM, SGB"; 471 | break; 472 | case 0xe5: 473 | chipSetInfo = "ROM, BS-X"; 474 | break; 475 | 476 | case 0x14: 477 | chipSetInfo = romSize > 0x0a ? "ROM, RAM, GSU-2" : "ROM, RAM, GSU-1"; 478 | hasRam = true; 479 | break; 480 | case 0x15: 481 | chipSetInfo = romSize > 0x0a ? "ROM, RAM, GSU-2, Battery" : "ROM, RAM, GSU-1, Battery"; 482 | hasRam = true; 483 | break; 484 | case 0x1a: 485 | chipSetInfo = "ROM, RAM, GSU-2-SP1, Battery"; 486 | hasRam = true; 487 | break; 488 | 489 | case 0xf3: 490 | if (chipsetSubtype == 0x10) { chipSetInfo = "ROM, CX4"; } 491 | break; 492 | case 0xf5: 493 | if (chipsetSubtype == 0x00) { chipSetInfo = "ROM, RAM, SPC7110, Battery"; } 494 | if (chipsetSubtype == 0x02) { chipSetInfo = "ROM, RAM, ST-018, Battery"; } 495 | hasRam = true; 496 | break; 497 | case 0xf6: 498 | if (chipsetSubtype == 0x01) { chipSetInfo = "ROM, ST-010/011, Battery"; } 499 | break; 500 | case 0xf9: 501 | if (chipsetSubtype == 0x00) { chipSetInfo = "ROM, RAM, SPC7110, RTC, Battery"; } 502 | hasRam = true; 503 | break; 504 | 505 | default: 506 | break; 507 | } 508 | 509 | if (gameCode == "XBND") { 510 | chipSetInfo = "ROM, RAM, Battery, XBand Modem"; 511 | hasRam = true; 512 | } 513 | if (gameCode == "MENU") { 514 | chipSetInfo = "ROM, RAM, Battery, MX15001TFC"; 515 | hasRam = true; 516 | } 517 | 518 | { 519 | ostringstream os; 520 | os << "1." << static_cast((uint8_t)header[0x2b]); 521 | version = os.str(); 522 | } 523 | 524 | switch (countryCode) { 525 | case 0x00: 526 | country = "Japan"; 527 | break; 528 | case 0x01: 529 | country = "USA"; 530 | break; 531 | case 0x02: 532 | country = "Europe"; 533 | break; 534 | case 0x03: 535 | country = "Sweden"; 536 | break; 537 | case 0x04: 538 | country = "Finland"; 539 | break; 540 | case 0x05: 541 | country = "Denmark"; 542 | break; 543 | case 0x06: 544 | country = "France"; 545 | break; 546 | case 0x07: 547 | country = "Holland"; 548 | break; 549 | case 0x08: 550 | country = "Spain"; 551 | break; 552 | case 0x09: 553 | country = "Germany"; 554 | break; 555 | case 0x0a: 556 | country = "Italy"; 557 | break; 558 | case 0x0b: 559 | country = "China/Hong Kong"; 560 | break; 561 | case 0x0c: 562 | country = "Indonesia"; 563 | break; 564 | case 0x0d: 565 | country = "South Korea"; 566 | break; 567 | case 0x0f: 568 | country = "Canada"; 569 | break; 570 | case 0x10: 571 | country = "Brazil"; 572 | break; 573 | case 0x11: 574 | country = "Australia"; 575 | break; 576 | default: 577 | country = "Unknown"; 578 | break; 579 | } 580 | } 581 | 582 | uint16_t sfcRom::calculateChecksum() const { 583 | vector img = image; 584 | putWord(img, headerLocation + 0x2c, 0xffff); 585 | putWord(img, headerLocation + 0x2e, 0x0000); 586 | 587 | size_t imageSize = img.size(); 588 | size_t mappedSize = 0; 589 | 590 | if (mapper == 0x0a && chipset == 0xf9 && chipsetSubtype == 0x00) { 591 | // Extended HiROM/SPC7110+RTC+Battery 592 | mappedSize = imageSize; 593 | } else if (mapper == 0x0a && chipset == 0xf5 && chipsetSubtype == 0x00) { 594 | // Extended HiROM/SPC7110+Battery 595 | mappedSize = imageSize > 0x200000 ? imageSize << 1 : imageSize; 596 | while (mappedSize > img.size()) { 597 | size_t remaining = mappedSize - img.size(); 598 | if (remaining > image.size()) { remaining = image.size(); } 599 | if ((remaining + img.size()) > mappedSize) { remaining = mappedSize - img.size(); } 600 | vector mirror(img.begin(), img.begin() + remaining); 601 | img.insert(img.end(), mirror.begin(), mirror.end()); 602 | } 603 | } else { 604 | // Standard mapping 605 | mappedSize = 1 << (correctedRomSize != 0 ? correctedRomSize + 10 : romSize + 10); 606 | while (mappedSize > img.size()) { 607 | vector mirror(img.begin() + (mappedSize >> 1), img.end()); 608 | img.insert(img.end(), mirror.begin(), mirror.end()); 609 | } 610 | } 611 | 612 | uint16_t sum = 0; 613 | size_t length = img.size(); 614 | for (size_t offset = 0; offset < length; ++offset) { 615 | sum += img[offset]; 616 | } 617 | return sum; 618 | } 619 | 620 | // Get little endian word 621 | uint16_t getWord(const vector& vec, size_t offset) { 622 | return (uint16_t)((vec[offset]) + ((uint8_t)(vec[offset + 1]) << 8)); 623 | } 624 | 625 | // Put little endian word 626 | void putWord(vector& vec, size_t offset, uint16_t value) { 627 | vec[offset] = (uint8_t)(value & 0xff); 628 | vec[offset + 1] = (uint8_t)(value >> 8); 629 | } 630 | 631 | // Opcodes used on reset 632 | bool validResetOpcode(uint8_t op) { 633 | switch (op) { 634 | case 0x18: // clc 635 | case 0x38: // sec 636 | case 0x4c: // jmp abs 637 | case 0x5c: // jml abs 638 | case 0x78: // sei 639 | case 0x80: // bra rel 640 | case 0x9c: // stz abs 641 | case 0xa0: // ldy #imm 642 | case 0xa9: // lda #imm 643 | case 0xc2: // rep 644 | case 0xd4: // pei (zp) 645 | case 0xd8: // cld 646 | case 0xdc: // jmp [abs long] 647 | case 0xe2: // sep 648 | case 0xe6: // inc zp 649 | case 0xea: // nop 650 | return true; 651 | default: 652 | return false; 653 | } 654 | } 655 | 656 | // SJIS subset used in SNES header 657 | string sjisToString(uint8_t code) { 658 | if (code >= 0x20 && code <= 0x7e) { 659 | return string(1, static_cast(code)); 660 | } else { 661 | switch (code) { 662 | case 0xa1: 663 | return "\uff61"; 664 | case 0xa2: 665 | return "\uff62"; 666 | case 0xa3: 667 | return "\uff63"; 668 | case 0xa4: 669 | return "\uff64"; 670 | case 0xa5: 671 | return "\uff65"; 672 | case 0xa6: 673 | return "\uff66"; 674 | case 0xa7: 675 | return "\uff67"; 676 | case 0xa8: 677 | return "\uff68"; 678 | case 0xa9: 679 | return "\uff69"; 680 | case 0xaa: 681 | return "\uff6a"; 682 | case 0xab: 683 | return "\uff6b"; 684 | case 0xac: 685 | return "\uff6c"; 686 | case 0xad: 687 | return "\uff6d"; 688 | case 0xae: 689 | return "\uff6e"; 690 | case 0xaf: 691 | return "\uff6f"; 692 | 693 | case 0xb0: 694 | return "\uff70"; 695 | case 0xb1: 696 | return "\uff71"; 697 | case 0xb2: 698 | return "\uff72"; 699 | case 0xb3: 700 | return "\uff73"; 701 | case 0xb4: 702 | return "\uff74"; 703 | case 0xb5: 704 | return "\uff75"; 705 | case 0xb6: 706 | return "\uff76"; 707 | case 0xb7: 708 | return "\uff77"; 709 | case 0xb8: 710 | return "\uff78"; 711 | case 0xb9: 712 | return "\uff79"; 713 | case 0xba: 714 | return "\uff7a"; 715 | case 0xbb: 716 | return "\uff7b"; 717 | case 0xbc: 718 | return "\uff7c"; 719 | case 0xbd: 720 | return "\uff7d"; 721 | case 0xbe: 722 | return "\uff7e"; 723 | case 0xbf: 724 | return "\uff7f"; 725 | 726 | case 0xc0: 727 | return "\uff80"; 728 | case 0xc1: 729 | return "\uff81"; 730 | case 0xc2: 731 | return "\uff82"; 732 | case 0xc3: 733 | return "\uff83"; 734 | case 0xc4: 735 | return "\uff84"; 736 | case 0xc5: 737 | return "\uff85"; 738 | case 0xc6: 739 | return "\uff86"; 740 | case 0xc7: 741 | return "\uff87"; 742 | case 0xc8: 743 | return "\uff88"; 744 | case 0xc9: 745 | return "\uff89"; 746 | case 0xca: 747 | return "\uff8a"; 748 | case 0xcb: 749 | return "\uff8b"; 750 | case 0xcc: 751 | return "\uff8c"; 752 | case 0xcd: 753 | return "\uff8d"; 754 | case 0xce: 755 | return "\uff8e"; 756 | case 0xcf: 757 | return "\uff8f"; 758 | 759 | case 0xd0: 760 | return "\uff90"; 761 | case 0xd1: 762 | return "\uff91"; 763 | case 0xd2: 764 | return "\uff92"; 765 | case 0xd3: 766 | return "\uff93"; 767 | case 0xd4: 768 | return "\uff94"; 769 | case 0xd5: 770 | return "\uff95"; 771 | case 0xd6: 772 | return "\uff96"; 773 | case 0xd7: 774 | return "\uff97"; 775 | case 0xd8: 776 | return "\uff98"; 777 | case 0xd9: 778 | return "\uff99"; 779 | case 0xda: 780 | return "\uff9a"; 781 | case 0xdb: 782 | return "\uff9b"; 783 | case 0xdc: 784 | return "\uff9c"; 785 | case 0xdd: 786 | return "\uff9d"; 787 | case 0xde: 788 | return "\uff9e"; 789 | case 0xdf: 790 | return "\uff9f"; 791 | 792 | default: 793 | return string(); 794 | } 795 | } 796 | } 797 | -------------------------------------------------------------------------------- /src/sfcRom.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct sfcRom { 8 | sfcRom(const std::string& path); 9 | 10 | std::string description(bool silent) const; 11 | std::string fix(const std::string& path, bool silent); 12 | 13 | bool isValid = false; 14 | bool hasIssues = false; 15 | bool hasSevereIssues = false; 16 | 17 | bool hasCopierHeader = false; 18 | bool hasCorrectTitle = false; 19 | bool hasCorrectRamSize = false; 20 | bool hasCorrectChecksum = false; 21 | bool hasLegalMode = false; 22 | bool hasKnownMapper = false; 23 | bool hasNewFormatHeader = false; 24 | 25 | std::string title; 26 | std::string mapperName; 27 | std::string chipSetInfo; 28 | std::string makerCode; 29 | std::string gameCode; 30 | std::string version; 31 | std::string country; 32 | 33 | uint8_t mode = 0; 34 | uint8_t mapper = 0; 35 | bool fast = false; 36 | bool hasRam = false; 37 | 38 | uint8_t chipset = 0; 39 | uint8_t chipsetSubtype = 0; 40 | uint8_t romSize = 0; 41 | uint8_t ramSize = 0; 42 | uint8_t countryCode = 0; 43 | 44 | uint16_t checksum = 0; 45 | uint16_t complement = 0; 46 | 47 | size_t imageSize = 0; 48 | size_t imageOffset = 0; 49 | size_t headerLocation = 0; 50 | 51 | uint8_t correctedMode = 0; 52 | uint8_t correctedRomSize = 0; 53 | uint16_t correctedChecksum = 0; 54 | uint16_t correctedComplement = 0; 55 | 56 | private: 57 | std::string filepath; 58 | std::vector image; 59 | 60 | void getHeaderInfo(const std::vector& header); 61 | int scoreHeaderLocation(size_t location) const; 62 | uint16_t calculateChecksum() const; 63 | }; 64 | -------------------------------------------------------------------------------- /src/superfamicheck.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sfcRom.hpp" 7 | 8 | using namespace std; 9 | 10 | bool fileAvailable(const std::string& path) { 11 | ifstream file(path, ios::in); 12 | return file.good(); 13 | } 14 | 15 | int main(int argc, const char* argv[]) { 16 | ez::ezOptionParser opt; 17 | opt.overview = "SuperFamicheck 1.1.0"; 18 | opt.syntax = "superfamicheck rom_file [options...]"; 19 | 20 | opt.add( 21 | "", // Default 22 | false, // Required 23 | 0, // Number of args expected 24 | 0, // Delimiter if expecting multiple args 25 | "Fix ROM image", "-f", "--fix" 26 | ); 27 | 28 | opt.add( 29 | "", // Default 30 | false, // Required 31 | 1, // Number of args expected 32 | 0, // Delimiter if expecting multiple args 33 | "Output ROM image path", "-o", "--out" 34 | ); 35 | 36 | opt.add( 37 | "", // Default 38 | false, // Required 39 | 0, // Number of args expected 40 | 0, // Delimiter if expecting multiple args 41 | "Silent operation (unless issues found)", "-s", "--semisilent" 42 | ); 43 | 44 | opt.add( 45 | "", // Default 46 | false, // Required 47 | 0, // Number of args expected 48 | 0, // Delimiter if expecting multiple args 49 | "Silent operation", "-S", "--silent" 50 | ); 51 | 52 | opt.add( 53 | "", // Default 54 | false, // Required 55 | 0, // Number of args expected 56 | 0, // Delimiter if expecting multiple args 57 | "Display instructions", "-h", "--help" 58 | ); 59 | 60 | string usage; 61 | vector badOptions, badArgs; 62 | opt.parse(argc, argv); 63 | opt.getUsage(usage); 64 | 65 | if (opt.isSet("-h")) { 66 | cout << usage; 67 | return 0; 68 | } 69 | 70 | bool silent = opt.isSet("-s"); 71 | bool verysilent = opt.isSet("-S"); 72 | 73 | if (!opt.gotRequired(badOptions)) { 74 | for (const auto& badOption : badOptions) { 75 | cerr << "Missing required option: " << badOption << "\n\n"; 76 | } 77 | std::cout << usage; 78 | return 1; 79 | } 80 | 81 | if (!opt.gotExpected(badOptions)) { 82 | for (const auto& badOption : badOptions) { 83 | cerr << "Missing argument for option: " << badOption << "\n\n"; 84 | } 85 | std::cout << usage; 86 | return 1; 87 | } 88 | 89 | if (!opt.gotValid(badOptions, badArgs)) { 90 | for (const auto& badOption : badOptions) { 91 | cerr << "Invalid argument for option: " << badOption << "\n\n"; 92 | } 93 | std::cout << usage; 94 | return 1; 95 | } 96 | 97 | string inputPath = string(); 98 | if (opt.firstArgs.size() > 1 || opt.lastArgs.size() > 0) { 99 | inputPath = opt.firstArgs.size() > 1 ? *opt.firstArgs.back() : *opt.lastArgs.front(); 100 | if (!fileAvailable(inputPath)) { 101 | cerr << "Cannot open file \"" << inputPath << "\"" << '\n'; 102 | return 1; 103 | } 104 | } else { 105 | cerr << "Missing required argument: rom_file" 106 | << "\n\n"; 107 | std::cout << usage; 108 | return 1; 109 | } 110 | 111 | sfcRom rom(inputPath); 112 | 113 | if (!verysilent) { cout << rom.description(silent); } 114 | 115 | if (rom.isValid && opt.isSet("-f")) { 116 | string outputPath = inputPath; 117 | if (opt.isSet("-o")) { opt.get("-o")->getString(outputPath); } 118 | 119 | string fixDescripton = rom.fix(outputPath, silent); 120 | if (!verysilent) { cout << fixDescripton; } 121 | } 122 | 123 | return 0; 124 | } 125 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(test LANGUAGES CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | if(NOT CMAKE_BUILD_TYPE) 7 | set(CMAKE_BUILD_TYPE Debug) 8 | endif() 9 | 10 | find_package(Catch2 3 QUIET) 11 | if(NOT Catch2_FOUND) 12 | Include(FetchContent) 13 | FetchContent_Declare( 14 | Catch2 15 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 16 | GIT_TAG v3.5.3 17 | ) 18 | FetchContent_MakeAvailable(Catch2) 19 | endif() 20 | 21 | set(SOURCES test.cpp ../src/sfcRom.cpp) 22 | add_executable(test ${SOURCES}) 23 | target_link_libraries(test PRIVATE Catch2::Catch2WithMain) 24 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR := build 2 | 3 | .PHONY: clean 4 | 5 | test: 6 | @cmake -B$(BUILD_DIR) 7 | @cmake --build $(BUILD_DIR) --parallel 4 8 | @$(BUILD_DIR)/test 9 | 10 | all: clean test 11 | 12 | clean: 13 | @rm -rf $(BUILD_DIR) 14 | -------------------------------------------------------------------------------- /test/data/public/rom0.sfc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Optiroc/SuperFamicheck/e99dcbf0c25500d1be3b5f738373daa76f3866d3/test/data/public/rom0.sfc -------------------------------------------------------------------------------- /test/data/public/rom1.sfc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Optiroc/SuperFamicheck/e99dcbf0c25500d1be3b5f738373daa76f3866d3/test/data/public/rom1.sfc -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include "../src/sfcRom.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // 8 | // Simple test ROMs 9 | // 10 | 11 | // rom0.sfc: 12 | // - 32Kb, code at 0x0000, mapped at 0x8000 13 | // - Reset vector 0x0000 14 | // - Not valid 15 | inline constexpr auto rom0 = "data/public/rom0.sfc"; 16 | 17 | // rom1.sfc: 18 | // - 32Kb 19 | // - Valid sfc 20 | // - ROM size 0x05 21 | // - Map mode 0x00 22 | // - Chipset 0x00 23 | // - Speed 200ns 24 | // - Bad checksum 25 | inline constexpr auto rom1 = "data/public/rom1.sfc"; 26 | 27 | // 28 | // ROMs not stored in repo 29 | // 30 | 31 | // Tales of Phantasia 32 | // ROM size 0x0d (8192KB, actual size 6144KB) 33 | // RAM size 0x03 (8KB) 34 | // Map mode 0x05 (Extended HiROM) 35 | // Chipset 0x02 (ROM, RAM, Battery) 36 | // Speed 120ns 37 | // Checksum 0x7c57 38 | // Complement 0x83a8 39 | inline constexpr auto rom_atvj = "data/private/atvj.sfc"; 40 | 41 | // Tales of Phantasia with bad checksum 42 | inline constexpr auto rom_atvj_bad = "data/private/atvj-bad.sfc"; 43 | 44 | // Daikaiju Monogatari II 45 | // ROM size 0x0d (8192KB, actual size 5120KB) 46 | // RAM size 0x03 (8KB) 47 | // Map mode 0x05 (Extended HiROM) 48 | // Chipset 0x55 (ROM, RAM, S-RTC, Battery) 49 | // Speed 120ns 50 | // Checksum 0x8528 51 | // Complement 0x7ad7 52 | inline constexpr auto rom_dkm2 = "data/private/dkm2.sfc"; 53 | 54 | // Metroid 3 55 | // ROM size 0x0c (4096KB, actual size 3072KB) 56 | // RAM size 0x03 (8KB) 57 | // Map mode 0x00 (LoROM) 58 | // Chipset 0x02 (ROM, RAM, Battery) 59 | // Speed 120ns 60 | // Checksum 0xf8df 61 | // Complement 0x0720 62 | inline constexpr auto rom_m3 = "data/private/m3.sfc"; 63 | 64 | // Momotaro Dentetsu Happy 65 | // ROM size 0x0c (4096KB, actual size 3072KB) 66 | // RAM size 0x03 (8KB) 67 | // Map mode 0x0a (Extended HiROM/SPC7110) 68 | // Chipset 0xf5 (ROM, RAM, SPC7110, Battery) 69 | // Speed 120ns 70 | // Checksum 0xe28c 71 | // Complement 0x1d73 72 | inline constexpr auto rom_mdh = "data/private/mdh.sfc"; 73 | 74 | // Super Mario World 75 | // ROM size 0x09 (512KB) 76 | // RAM size 0x01 (2KB) 77 | // Map mode 0x00 (LoROM) 78 | // Chipset 0x02 (ROM, RAM, Battery) 79 | // Speed 200ns 80 | // Checksum 0xa0da 81 | // Complement 0x5f25 82 | inline constexpr auto rom_smw = "data/private/smw.sfc"; 83 | 84 | // Super Mario World 2 85 | // ROM size 0x0b (2048KB) 86 | // RAM size 0x00 (1KB) 87 | // Map mode 0x00 (LoROM) 88 | // Chipset 0x15 (ROM, RAM, GSU-2, Battery) 89 | // Speed 200ns 90 | // Checksum 0x132c 91 | // Complement 0xecd3 92 | inline constexpr auto rom_smw2 = "data/private/smw2.sfc"; 93 | 94 | // Star Ocean 95 | // ROM size 0x0d (8192KB, actual size 6144KB) 96 | // RAM size 0x03 (8KB) 97 | // Map mode 0x02 (LoROM/S-DD1) 98 | // Chipset 0x45 (ROM, RAM, S-DD1, Battery) 99 | // Speed 120ns 100 | // Checksum 0x13b8 101 | // Complement 0xec47 102 | inline constexpr auto rom_so = "data/private/so.sfc"; 103 | 104 | // Super Power League 4 105 | // ROM size 0x0b (2048KB) 106 | // RAM size 0x03 (8KB) 107 | // Map mode 0x0a (Extended HiROM/SPC7110) 108 | // Chipset 0xf5 (ROM, RAM, SPC7110, Battery) 109 | // Speed 120ns 110 | // Checksum 0x01aa 111 | // Complement 0xfe55 112 | inline constexpr auto rom_spl4 = "data/private/spl4.sfc"; 113 | 114 | // Tengai Makyou Zero 115 | // ROM size 0x0d (8192KB, actual size 5120KB) 116 | // RAM size 0x03 (8KB) 117 | // Map mode 0x0a (Extended HiROM/SPC7110) 118 | // Chipset 0xf9 (ROM, RAM, SPC7110, RTC, Battery) 119 | // Speed 120ns 120 | // Checksum 0xde89 121 | // Complement 0x2176 122 | inline constexpr auto rom_tmz = "data/private/tmz.sfc"; 123 | 124 | bool file_exists(const std::string& path) { 125 | return std::filesystem::is_regular_file(std::filesystem::path(path)); 126 | } 127 | 128 | bool rom_isValid(const std::string& path, bool expected) { 129 | if (!file_exists(path)) { 130 | std::cout << "File '" << path << "' not found, skipping\n"; 131 | return expected; 132 | } 133 | sfcRom rom(path); 134 | return rom.isValid; 135 | } 136 | 137 | bool rom_hasCorrectChecksum(const std::string& path, bool expected) { 138 | if (!file_exists(path)) { 139 | std::cout << "File '" << path << "' not found, skipping\n"; 140 | return expected; 141 | } 142 | sfcRom rom(path); 143 | return rom.hasCorrectChecksum; 144 | } 145 | 146 | TEST_CASE("sfcRom.isValid") { 147 | REQUIRE(rom_isValid(rom0, false) == false); 148 | REQUIRE(rom_isValid(rom1, true) == true); 149 | REQUIRE(rom_isValid(rom_atvj, true) == true); 150 | REQUIRE(rom_isValid(rom_atvj_bad, true) == true); 151 | REQUIRE(rom_isValid(rom_dkm2, true) == true); 152 | REQUIRE(rom_isValid(rom_m3, true) == true); 153 | REQUIRE(rom_isValid(rom_mdh, true) == true); 154 | REQUIRE(rom_isValid(rom_smw, true) == true); 155 | REQUIRE(rom_isValid(rom_smw2, true) == true); 156 | REQUIRE(rom_isValid(rom_so, true) == true); 157 | REQUIRE(rom_isValid(rom_spl4, true) == true); 158 | REQUIRE(rom_isValid(rom_tmz, true) == true); 159 | } 160 | 161 | TEST_CASE("sfcRom.hasCorrectChecksum") { 162 | REQUIRE(rom_hasCorrectChecksum(rom0, false) == false); 163 | REQUIRE(rom_hasCorrectChecksum(rom1, false) == false); 164 | REQUIRE(rom_hasCorrectChecksum(rom_atvj, true) == true); 165 | REQUIRE(rom_hasCorrectChecksum(rom_atvj_bad, false) == false); 166 | REQUIRE(rom_hasCorrectChecksum(rom_dkm2, true) == true); 167 | REQUIRE(rom_hasCorrectChecksum(rom_m3, true) == true); 168 | REQUIRE(rom_hasCorrectChecksum(rom_mdh, true) == true); 169 | REQUIRE(rom_hasCorrectChecksum(rom_smw, true) == true); 170 | REQUIRE(rom_hasCorrectChecksum(rom_smw2, true) == true); 171 | REQUIRE(rom_hasCorrectChecksum(rom_so, true) == true); 172 | REQUIRE(rom_hasCorrectChecksum(rom_spl4, true) == true); 173 | REQUIRE(rom_hasCorrectChecksum(rom_tmz, true) == true); 174 | } 175 | --------------------------------------------------------------------------------