├── TODO ├── .gitignore ├── err_shim.h ├── mapfile.h ├── Makefile ├── Makefile.win ├── README.md ├── LICENSE ├── sram.h ├── wingetopt.h ├── mapfile.c ├── gbcamextract.c └── wingetopt.c /TODO: -------------------------------------------------------------------------------- 1 | -Autodetect ROM and .sav types 2 | -Set image metadata according to in-SRAM data 3 | -Web version 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | *.la 11 | *.lo 12 | 13 | # Shared objects (inc. Windows DLLs) 14 | *.dll 15 | *.so 16 | *.so.* 17 | *.dylib 18 | 19 | # Executables 20 | *.exe 21 | *.out 22 | *.app 23 | *.i*86 24 | *.x86_64 25 | *.hex 26 | 27 | # Pictures 28 | *.png 29 | 30 | # Game Boy things 31 | *.gb 32 | *.sav 33 | 34 | # vim swapfiles 35 | *.swp 36 | 37 | gbcamextract 38 | -------------------------------------------------------------------------------- /err_shim.h: -------------------------------------------------------------------------------- 1 | #ifndef __ERR_SHIM_H__ 2 | #define __ERR_SHIM_H__ 3 | 4 | #ifdef __MINGW32__ 5 | #define warn(...) do {fprintf(stderr, __VA_ARGS__); fprintf(stderr, ": %s\n", strerror(errno));} while (0); 6 | #define warnx(...) do {fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");} while (0); 7 | #define err(eval, ...) do {fprintf(stderr, __VA_ARGS__); fprintf(stderr, ": %s\n", strerror(errno)); exit(eval);} while (0); 8 | #define errx(eval, ...) do {fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(eval);} while (0); 9 | #else 10 | #include 11 | #endif 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /mapfile.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAPFILE_H_ 2 | #define _MAPFILE_H_ 3 | 4 | #ifdef __MINGW32__ 5 | #include 6 | #endif 7 | #include 8 | #include 9 | 10 | struct MappedFile_s { 11 | void *data; 12 | uint64_t size; 13 | #ifdef __MINGW32__ 14 | HANDLE _hFile; 15 | HANDLE _hMapping; 16 | #else 17 | int _fd; 18 | #endif 19 | }; 20 | 21 | struct MappedFile_s MappedFile_Create(char *filename, size_t size); 22 | struct MappedFile_s MappedFile_Open(char *filename, bool writable); 23 | void MappedFile_Close(struct MappedFile_s m); 24 | 25 | /* _MAPFILE_H_ */ 26 | #endif 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | target ?= gbcamextract 2 | VERSION_STRING= 1.1 3 | objects := $(patsubst %.c,%.o,$(wildcard *.c)) 4 | 5 | LDLIBS += -lpng 6 | 7 | CFLAGS += -std=gnu99 -Os -ggdb -D__progversion=\"${VERSION_STRING}\" -D__progname=\"${target}\" 8 | 9 | #EXTRAS += -fsanitize=undefined -fsanitize=null -fcf-protection=full -fstack-protector-all -fstack-check -Wimplicit-fallthrough -fanalyzer -Wall 10 | EXTRAS += -fanalyzer -Wall -flto 11 | 12 | CFLAGS += ${EXTRAS} 13 | LDFLAGS += ${EXTRAS} 14 | 15 | .PHONY: all 16 | all: $(target) 17 | 18 | .PHONY: clean 19 | clean: 20 | rm -f $(target) $(target).exe $(objects) 21 | 22 | .PHONY: install 23 | install: 24 | cp $(target) /usr/local/bin/$(target) 25 | 26 | $(target): $(objects) 27 | -------------------------------------------------------------------------------- /Makefile.win: -------------------------------------------------------------------------------- 1 | target ?= gbcamextract 2 | VERSION_STRING= 1.1 3 | objects := $(patsubst %.c,%.o,$(wildcard *.c)) 4 | 5 | LDLIBS += -Wl,-Bstatic -l:libpng.a -Wl,-Bstatic -l:libz.a 6 | 7 | CFLAGS += -std=gnu99 -Os -ggdb -D__progversion=\"${VERSION_STRING}\" -D__progname=\"${target}\" 8 | 9 | #EXTRAS += -fsanitize=undefined -fsanitize=null -fcf-protection=full -fstack-protector-all -fstack-check -Wimplicit-fallthrough -fanalyzer -Wall 10 | EXTRAS += -fanalyzer -Wall -flto 11 | 12 | CFLAGS += ${EXTRAS} 13 | LDFLAGS += ${EXTRAS} 14 | 15 | .PHONY: all 16 | all: $(target) 17 | 18 | .PHONY: clean 19 | clean: 20 | rm -f $(target) $(target).exe $(objects) 21 | 22 | .PHONY: install 23 | install: 24 | cp $(target) /usr/local/bin/$(target) 25 | 26 | $(target): $(objects) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gbcamextract 2 | ============ 3 | 4 | Extracts photos from Game Boy Camera / Pocket Camera saves. Frames can be preserved. The Hello Kitty camera is supported too. 5 | 6 | ## Usage 7 | 8 | ```console 9 | gbcamextract [-r rom.gb] -s save.sav 10 | ``` 11 | 12 | This will produce 30 PNG files containing your photos. It is optional to specify the rom; this will allow the picture frames to be extracted too. 13 | 14 | ## Building 15 | 16 | You will first need to install [libpng](http://www.libpng.org/pub/png/libpng.html). 17 | 18 | Then, for Linux: 19 | ```console 20 | make 21 | ``` 22 | 23 | Or for Windows: 24 | ```console 25 | mingw32-make -f Makefile.win 26 | ``` 27 | 28 | 29 | ## License 30 | 31 | Licensed under the expat license, which is sometimes called the MIT license. 32 | See the `LICENSE` file for details. 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2020 jkbenaim et al. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /sram.h: -------------------------------------------------------------------------------- 1 | #ifndef _SRAM_H_ 2 | #define _SRAM_H_ 3 | 4 | #include 5 | 6 | struct image_metadata_s { 7 | uint32_t userid; 8 | uint8_t username[9]; 9 | uint8_t blood_sex; // 0h = no gender, 1h = male, 2h = female, 4h = A, 8h = B, Ch = O, 10h = AB 10 | uint32_t birthdate; 11 | uint8_t pad12[3]; // unknown content 12 | uint8_t comment[0x30-0x15]; 13 | uint8_t pad30[3]; // always 00h 14 | uint8_t copied; // 00h = original, 01h = copy 15 | uint16_t checksum2; // maybe? 16 | uint8_t pad36[0x54-0x36]; // always 00h 17 | uint8_t border; 18 | uint8_t magic[5]; // ASCII "Magic" without null byte 19 | uint16_t checksum; 20 | } __attribute__((packed)); 21 | 22 | struct user_metadata_s { 23 | uint32_t userid; 24 | uint8_t username[9]; 25 | uint8_t blood_sex; 26 | uint32_t birhdate; 27 | uint8_t magic[5]; // ASCII "Magic" without null byte 28 | uint16_t checksum; 29 | } __attribute__((packed)); 30 | 31 | struct slot_s { 32 | uint8_t image[0xE00]; 33 | uint8_t thumbnail[0x100]; 34 | struct image_metadata_s imagemeta; 35 | struct image_metadata_s imagemeta2; 36 | struct user_metadata_s usermeta; 37 | struct user_metadata_s usermeta2; 38 | uint8_t padFEA[17]; // always AAh 39 | uint8_t padFFA[5]; // trash 40 | } __attribute__((packed)); 41 | 42 | struct firstslot_s { 43 | uint8_t image[0x1000]; 44 | uint8_t pad[0x11b2-0x1000]; 45 | uint8_t vec[0x11d0-0x11b2]; 46 | uint8_t magic[5]; // ASCI "Magic" without null byte 47 | uint16_t checksum; 48 | } __attribute__((packed)); 49 | 50 | #endif 51 | 52 | -------------------------------------------------------------------------------- /wingetopt.h: -------------------------------------------------------------------------------- 1 | #ifdef __MINGW32__ 2 | #ifndef __GETOPT_H__ 3 | /** 4 | * DISCLAIMER 5 | * This file has no copyright assigned and is placed in the Public Domain. 6 | * This file is a part of the w64 mingw-runtime package. 7 | * 8 | * The w64 mingw-runtime package and its code is distributed in the hope that it 9 | * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR 10 | * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to 11 | * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 | */ 13 | 14 | #define __GETOPT_H__ 15 | 16 | /* All the headers include this file. */ 17 | #include 18 | 19 | #if defined( WINGETOPT_SHARED_LIB ) 20 | # if defined( BUILDING_WINGETOPT_DLL ) 21 | # define WINGETOPT_API __declspec(dllexport) 22 | # else 23 | # define WINGETOPT_API __declspec(dllimport) 24 | # endif 25 | #else 26 | # define WINGETOPT_API 27 | #endif 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | WINGETOPT_API extern int optind; /* index of first non-option in argv */ 34 | WINGETOPT_API extern int optopt; /* single option character, as parsed */ 35 | WINGETOPT_API extern int opterr; /* flag to enable built-in diagnostics... */ 36 | /* (user may set to zero, to suppress) */ 37 | 38 | WINGETOPT_API extern char *optarg; /* pointer to argument of current option */ 39 | 40 | extern int getopt(int nargc, char * const *nargv, const char *options); 41 | 42 | #ifdef _BSD_SOURCE 43 | /* 44 | * BSD adds the non-standard `optreset' feature, for reinitialisation 45 | * of `getopt' parsing. We support this feature, for applications which 46 | * proclaim their BSD heritage, before including this header; however, 47 | * to maintain portability, developers are advised to avoid it. 48 | */ 49 | # define optreset __mingw_optreset 50 | extern int optreset; 51 | #endif 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | /* 56 | * POSIX requires the `getopt' API to be specified in `unistd.h'; 57 | * thus, `unistd.h' includes this header. However, we do not want 58 | * to expose the `getopt_long' or `getopt_long_only' APIs, when 59 | * included in this manner. Thus, close the standard __GETOPT_H__ 60 | * declarations block, and open an additional __GETOPT_LONG_H__ 61 | * specific block, only when *not* __UNISTD_H_SOURCED__, in which 62 | * to declare the extended API. 63 | */ 64 | #endif /* !defined(__GETOPT_H__) */ 65 | 66 | #if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) 67 | #define __GETOPT_LONG_H__ 68 | 69 | #ifdef __cplusplus 70 | extern "C" { 71 | #endif 72 | 73 | struct option /* specification for a long form option... */ 74 | { 75 | const char *name; /* option name, without leading hyphens */ 76 | int has_arg; /* does it take an argument? */ 77 | int *flag; /* where to save its status, or NULL */ 78 | int val; /* its associated status value */ 79 | }; 80 | 81 | enum /* permitted values for its `has_arg' field... */ 82 | { 83 | no_argument = 0, /* option never takes an argument */ 84 | required_argument, /* option always requires an argument */ 85 | optional_argument /* option may take an argument */ 86 | }; 87 | 88 | extern int getopt_long(int nargc, char * const *nargv, const char *options, 89 | const struct option *long_options, int *idx); 90 | extern int getopt_long_only(int nargc, char * const *nargv, const char *options, 91 | const struct option *long_options, int *idx); 92 | /* 93 | * Previous MinGW implementation had... 94 | */ 95 | #ifndef HAVE_DECL_GETOPT 96 | /* 97 | * ...for the long form API only; keep this for compatibility. 98 | */ 99 | # define HAVE_DECL_GETOPT 1 100 | #endif 101 | 102 | #ifdef __cplusplus 103 | } 104 | #endif 105 | 106 | #endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */ 107 | #endif 108 | -------------------------------------------------------------------------------- /mapfile.c: -------------------------------------------------------------------------------- 1 | #ifdef __MINGW32__ 2 | #include 3 | #include 4 | #include 5 | #include "mapfile.h" 6 | 7 | struct MappedFile_s MappedFile_Create(char *filename, size_t size) 8 | { 9 | __label__ out_error, out_ok; 10 | LPVOID p; 11 | BOOL rc; 12 | LARGE_INTEGER liSize; 13 | struct MappedFile_s m; 14 | DWORD dw; 15 | char *lpMsgBuf; 16 | 17 | m._hFile = CreateFile( 18 | filename, 19 | GENERIC_READ | GENERIC_WRITE, 20 | FILE_SHARE_READ | FILE_SHARE_WRITE, 21 | NULL, 22 | CREATE_ALWAYS, 23 | FILE_ATTRIBUTE_NORMAL, 24 | NULL 25 | ); 26 | 27 | if (m._hFile == INVALID_HANDLE_VALUE) { 28 | goto out_error; 29 | } 30 | 31 | liSize.QuadPart = size; 32 | m.size = size; 33 | rc = SetFilePointerEx( 34 | m._hFile, 35 | liSize, 36 | NULL, 37 | FILE_BEGIN 38 | ); 39 | if (rc == 0) { 40 | goto out_error; 41 | } 42 | 43 | rc = SetEndOfFile(m._hFile); 44 | if (rc == 0) { 45 | goto out_error; 46 | } 47 | 48 | m._hMapping = CreateFileMapping( 49 | m._hFile, 50 | NULL, 51 | PAGE_READWRITE, 52 | liSize.HighPart, 53 | liSize.LowPart, 54 | NULL 55 | ); 56 | 57 | if (m._hMapping == INVALID_HANDLE_VALUE) { 58 | goto out_error; 59 | } 60 | 61 | p = MapViewOfFile( 62 | m._hMapping, 63 | FILE_MAP_ALL_ACCESS, 64 | 0, 65 | 0, 66 | 0 67 | ); 68 | 69 | if (p == NULL) { 70 | goto out_error; 71 | } 72 | 73 | m.data = (void *) p; 74 | goto out_ok; 75 | 76 | out_error: 77 | dw = GetLastError(); 78 | FormatMessage( 79 | FORMAT_MESSAGE_ALLOCATE_BUFFER | 80 | FORMAT_MESSAGE_FROM_SYSTEM | 81 | FORMAT_MESSAGE_IGNORE_INSERTS, 82 | NULL, 83 | dw, 84 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 85 | (LPTSTR) &lpMsgBuf, 86 | 0, 87 | NULL 88 | ); 89 | printf("error in MappedFile_Create: %s", lpMsgBuf); 90 | LocalFree(lpMsgBuf); 91 | m.data = NULL; 92 | out_ok: 93 | return m; 94 | } 95 | 96 | struct MappedFile_s MappedFile_Open(char *filename, bool writable) 97 | { 98 | __label__ out_error, out_ok; 99 | LPVOID p; 100 | BOOL rc; 101 | LARGE_INTEGER liSize; 102 | struct MappedFile_s m; 103 | 104 | m._hFile = CreateFile( 105 | filename, 106 | writable ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ, 107 | FILE_SHARE_READ | FILE_SHARE_WRITE, 108 | NULL, 109 | OPEN_EXISTING, 110 | FILE_ATTRIBUTE_NORMAL, 111 | NULL 112 | ); 113 | 114 | if (m._hFile == INVALID_HANDLE_VALUE) { 115 | goto out_error; 116 | } 117 | 118 | rc = GetFileSizeEx(m._hFile, &liSize); 119 | if (rc == 0) { 120 | goto out_error; 121 | } 122 | m.size = (uint64_t) liSize.QuadPart; 123 | 124 | m._hMapping = CreateFileMapping( 125 | m._hFile, 126 | NULL, 127 | writable ? PAGE_READWRITE : PAGE_READONLY, 128 | 0, 129 | m.size, 130 | NULL 131 | ); 132 | 133 | if (m._hMapping == INVALID_HANDLE_VALUE) { 134 | goto out_error; 135 | } 136 | 137 | p = MapViewOfFile( 138 | m._hMapping, 139 | writable ? FILE_MAP_ALL_ACCESS : FILE_MAP_COPY, 140 | 0, 141 | 0, 142 | 0 143 | ); 144 | 145 | if (p == NULL) { 146 | goto out_error; 147 | } 148 | 149 | m.data = (void *) p; 150 | goto out_ok; 151 | 152 | out_error: 153 | m.data = NULL; 154 | out_ok: 155 | return m; 156 | } 157 | 158 | void MappedFile_Close(struct MappedFile_s m) 159 | { 160 | FlushViewOfFile((LPCVOID) m.data, 0); 161 | UnmapViewOfFile((LPCVOID) m.data); 162 | CloseHandle(m._hMapping); 163 | CloseHandle(m._hFile); 164 | } 165 | 166 | /* __MINGW32__ */ 167 | #else 168 | #include 169 | #include 170 | #include 171 | #include 172 | #include 173 | #include 174 | #include 175 | #include 176 | #include "mapfile.h" 177 | 178 | struct MappedFile_s MappedFile_Create(char *filename, size_t size) 179 | { 180 | __label__ out_error, out_ok, out_close; 181 | struct MappedFile_s m; 182 | 183 | m._fd = open(filename, O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR); 184 | if (!m._fd) goto out_error; 185 | close(m._fd); 186 | if (truncate(filename, size) < 0) goto out_error; 187 | m._fd = open(filename, O_RDWR); 188 | if (m._fd == -1) goto out_error; 189 | 190 | m.data = mmap( 191 | NULL, 192 | size, 193 | PROT_READ|PROT_WRITE, 194 | MAP_SHARED, 195 | m._fd, 196 | 0 197 | ); 198 | if (m.data == MAP_FAILED) { 199 | goto out_close; 200 | } 201 | 202 | memset(m.data, 0, size); 203 | m.size = size; 204 | 205 | goto out_ok; 206 | 207 | out_close: 208 | close(m._fd); 209 | m._fd = -1; 210 | out_error: 211 | m.size = 0; 212 | m.data = NULL; 213 | out_ok: 214 | return m; 215 | } 216 | 217 | struct MappedFile_s MappedFile_Open(char *filename, bool writable) 218 | { 219 | __label__ out_error, out_ok, out_close; 220 | struct MappedFile_s m; 221 | struct stat sb; 222 | 223 | if (stat(filename, &sb) == -1) { 224 | goto out_error; 225 | } 226 | m.size = sb.st_size; 227 | 228 | m._fd = open(filename, writable ? O_RDWR : O_RDONLY); 229 | if (m._fd == -1) { 230 | goto out_error; 231 | } 232 | 233 | m.data = mmap( 234 | NULL, 235 | sb.st_size, 236 | PROT_READ|PROT_WRITE, 237 | writable ? MAP_SHARED : MAP_PRIVATE, 238 | m._fd, 239 | 0 240 | ); 241 | if (m.data == MAP_FAILED) { 242 | goto out_close; 243 | } 244 | 245 | goto out_ok; 246 | 247 | out_close: 248 | close(m._fd); 249 | m._fd = -1; 250 | out_error: 251 | m.size = 0; 252 | m.data = NULL; 253 | out_ok: 254 | return m; 255 | } 256 | 257 | void MappedFile_Close(struct MappedFile_s m) 258 | { 259 | munmap(m.data, m.size); 260 | close(m._fd); 261 | } 262 | 263 | /* __MINGW32__ */ 264 | #endif 265 | -------------------------------------------------------------------------------- /gbcamextract.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2020 jkbenaim et al. 3 | * 4 | * This program is free software; you may redistribute and/or modify it under 5 | * the terms of the expat license (also known as the "MIT license"). 6 | * 7 | * This program is distributed in the hope that it will be useful, but without 8 | * any warranty; without even the implied warranty of merchantability or 9 | * firness for a particular purpose. 10 | * 11 | * For the full license text, see LICENSE. 12 | * 13 | ****************************************************************************** 14 | * 15 | * This file implements main() and all of the Game Boy-specific functions. 16 | * 17 | */ 18 | 19 | #include "err_shim.h" 20 | #include // errno 21 | #include 22 | #include 23 | #include 24 | #include // printf, fopen, fclose, fread 25 | #include // malloc, EXIT_SUCCESS, EXIT_FAILURE, NULL 26 | #include // strerror 27 | #include 28 | #include "mapfile.h" 29 | #include "sram.h" 30 | #include "wingetopt.h" 31 | 32 | const int HELLO_KITTY_FRAME_OFFSETS[25][2] = {{0xC6C70, 0xCF5D0}, {0xC3B80, 0xCF548}, {0xCBEC0, 0xCF4C0}, {0xC5F10, 0xCF658}, {0xCF210, 0xCF7F0}, {0xC73A0, 0xCF768}, {0xB7420, 0xCF6E0}, {0xBE3E0, 0xCF438}, {0xB3CD0, 0xC7EF0}, {0xB2B80, 0xCF3B0}, {0x8FD50, 0xC7F78}, {0xC3800, 0xD7800}, {0xBDC00, 0xD3F70}, {0xD7F70, 0xD7888}, {0xC5C00, 0xD7998}, {0xB7C20, 0xD7910}, {0xC3ED0, 0xD3D50}, {0x33F80, 0xD3CC8}, {0xDB800, 0xD3DD8}, {0xB2200, 0xD3EE8}, {0xB34D0, 0xD3E60}, {0xB3030, 0xD7A20}, {0x93E00, 0xD7D50}, {0x77FE0, 0xCFCB8}, {0x77FF0, 0xCFDC4}}; 33 | #define ROM_TITLE_OFFSET 0x134 34 | #define ROM_TITLE_LENGTH 0xF 35 | 36 | #define BANK_SIZE 0x4000 37 | #define BANK(bank) BANK_SIZE * bank 38 | 39 | const int FILE_ERROR = 2; 40 | const int FILE_SIZE_ERROR = 3; 41 | 42 | const int WIDTH = 160; 43 | const int ROW_SIZE = 40; // WIDTH/4: 2 bits per pixel means 4 pixels per byte 44 | const int HEIGHT = 144; 45 | const int SAVEGAME_SIZE = 128*1024; 46 | const int ROM_BUFFER_SIZE = 1024*1024; 47 | 48 | static inline int picNum2BaseAddress(int picNum); 49 | uint8_t *getFrame(uint8_t rom[], int frameNumber); 50 | static inline unsigned int interleaveBytes(uint8_t low, uint8_t high); 51 | void convert(uint8_t framesBuffer[], uint8_t saveBuffer[], uint8_t pixelBuffer[], int picNum); 52 | void writeImageFile(uint8_t pixelBuffer[], const char *filename); 53 | void drawSpan(uint8_t pixelBuffer[], uint8_t *buffer, int x, int y); 54 | void readData(uint8_t *fileName, uint8_t *buffer, int offset); 55 | bool isGbRom(const uint8_t data[0x150]); 56 | bool isHkRom(const uint8_t rom[0x150]); 57 | static void usage(void); 58 | static void version(void); 59 | 60 | bool isGbRom(const uint8_t data[0x150]) 61 | { 62 | const uint8_t sig[] = {0xce, 0xed, 0x66, 0x66}; 63 | if (!memcmp(data + 0x104, sig, 4)) 64 | return true; 65 | else 66 | return false; 67 | } 68 | 69 | int getPicNumForSlotNum(const uint8_t *save, int slotNum) 70 | { 71 | int picNum; 72 | struct firstslot_s *firstslot = (struct firstslot_s *)save; 73 | if ((slotNum < 1) || (slotNum > 30)) 74 | return -1; 75 | picNum = firstslot->vec[slotNum-1]; 76 | switch (picNum) { 77 | case 0 ... 29: 78 | picNum++; 79 | break; 80 | case 255: 81 | default: 82 | picNum = -1; 83 | break; 84 | } 85 | 86 | return picNum; 87 | } 88 | 89 | int main(int argc, char *argv[]) 90 | { 91 | char *filename_save = NULL; 92 | char *filename_rom = NULL; 93 | int rc; 94 | struct MappedFile_s mSave = {0}; 95 | struct MappedFile_s mRom = {0}; 96 | uint8_t pixelBuffer[ROW_SIZE*HEIGHT]; 97 | 98 | while ((rc = getopt(argc, argv, "s:r:V")) != -1) 99 | switch (rc) { 100 | case 's': 101 | if (filename_save) { 102 | usage(); 103 | return EXIT_FAILURE; 104 | } 105 | filename_save = optarg; 106 | break; 107 | case 'r': 108 | if (filename_rom) { 109 | usage(); 110 | return EXIT_FAILURE; 111 | } 112 | filename_rom = optarg; 113 | break; 114 | case 'V': 115 | version(); 116 | return EXIT_FAILURE; 117 | break; 118 | default: 119 | usage(); 120 | return EXIT_FAILURE; 121 | break; 122 | } 123 | argc -= optind; 124 | argv += optind; 125 | if (*argv != NULL) { 126 | usage(); 127 | return EXIT_FAILURE; 128 | } 129 | 130 | if (!filename_save) { 131 | usage(); 132 | return EXIT_FAILURE; 133 | } 134 | 135 | // Open the save file. 136 | mSave = MappedFile_Open(filename_save, false); 137 | if (!mSave.data) 138 | err(1, "couldn't open save for reading"); 139 | 140 | // Check the savegame's size. 141 | if (mSave.size != 131072) 142 | errx(1, "savegame has weird size"); 143 | 144 | // Check if the save that we read is actually a rom. 145 | if (isGbRom(mSave.data)) 146 | errx(1, "save expected, but rom was given"); 147 | 148 | // If a rom was given, open it. 149 | if (filename_rom) { 150 | mRom = MappedFile_Open(filename_rom, false); 151 | if (!mRom.data) 152 | err(1, "couldn't open rom for reading"); 153 | if (mRom.size != 1048576) 154 | errx(1, "rom has weird size"); 155 | if (!isGbRom(mRom.data)) 156 | errx(1, "rom given doesn't look like a real rom"); 157 | } 158 | 159 | 160 | // convert 161 | memset(pixelBuffer, 0, ROW_SIZE*HEIGHT); // set pixelBuffer to all black 162 | 163 | for (int slotNum = 1; slotNum <= 30; ++slotNum) 164 | { 165 | int picNum = getPicNumForSlotNum(mSave.data, slotNum); 166 | char filename[16] = {0}; 167 | convert(mRom.data, mSave.data, pixelBuffer, slotNum); 168 | if (picNum != -1) 169 | snprintf(filename, 15, "IMG_%02d.png", picNum); 170 | else 171 | snprintf(filename, 15, "DEL_%02d.png", slotNum); 172 | filename[15] = '\0'; 173 | writeImageFile(pixelBuffer, filename); 174 | } 175 | 176 | // Return 177 | return EXIT_SUCCESS; 178 | } 179 | 180 | bool isHkRom(const uint8_t rom[0x150]) 181 | { 182 | if (!memcmp(rom + ROM_TITLE_OFFSET, "POCKETCAMERA_SN", ROM_TITLE_LENGTH)) 183 | return true; 184 | else 185 | return false; 186 | } 187 | 188 | uint8_t *getFrame(uint8_t rom[], int frameNumber) 189 | { 190 | int frameAddress; 191 | 192 | if (!rom) { 193 | return NULL; 194 | } 195 | 196 | if (isHkRom(rom)) 197 | { 198 | // validate the frame number, hello kitty version has 25 frames 199 | if( frameNumber < 0 || frameNumber >= 25 ) 200 | frameNumber = 24; 201 | 202 | // retrieve the border address 203 | frameAddress = HELLO_KITTY_FRAME_OFFSETS[frameNumber][0]; 204 | } 205 | else 206 | { 207 | // validate the frame number 208 | if (frameNumber < 0 || frameNumber >= 18) 209 | frameNumber = 13; 210 | 211 | // calculate the border address. 212 | // it can be in one of two banks. 213 | if(frameNumber < 9) 214 | frameAddress = BANK(0x34) + frameNumber * 0x688; 215 | else 216 | frameAddress = BANK(0x35) + (frameNumber - 9) * 0x688; 217 | } 218 | 219 | return &rom[frameAddress]; 220 | } 221 | 222 | static inline int picNum2BaseAddress(int picNum) 223 | { 224 | // Picture 1 is at 0x2000, picture 2 is at 0x3000, etc. 225 | return (picNum + 1) * 0x1000; 226 | } 227 | 228 | void convert(uint8_t rom[], uint8_t saveBuffer[], uint8_t pixelBuffer[], int picNum) 229 | { 230 | int baseAddress = picNum2BaseAddress(picNum); 231 | int frameNumber = saveBuffer[baseAddress + 0xfb0]; 232 | uint8_t *frameAddress = getFrame(rom, frameNumber); 233 | int xTile, yTile, tileNum, x, y, z; 234 | uint8_t *tile; 235 | 236 | for (yTile = 0; yTile < 14; ++yTile) 237 | { 238 | y = 16 + yTile*8; 239 | x = 16; 240 | tile = saveBuffer + baseAddress + yTile*256; 241 | for (x = 16; x <= 8*17; tile+=16, x+=8) 242 | { 243 | drawSpan(pixelBuffer, tile, x, y); 244 | } 245 | 246 | if (rom) { 247 | // Draw the sides of the frame 248 | y = 16 + yTile*8; 249 | for (z=0; z<4; ++z) 250 | { 251 | if (isHkRom(rom)) { 252 | tileNum = rom[HELLO_KITTY_FRAME_OFFSETS[frameNumber][1] + 0x50 + yTile*4 + z]; 253 | } else { 254 | tileNum = getFrame(rom, frameNumber)[0x650 + yTile*4 + z]; 255 | } 256 | tile = getFrame(rom, frameNumber) + tileNum*16; 257 | x = ((z&1)?8:0) + ((z&2)?HEIGHT:0); 258 | drawSpan(pixelBuffer, tile, x, y); 259 | } 260 | } 261 | } 262 | 263 | if (rom) { 264 | // Draw the top and bottom of the frame 265 | for (xTile=0; xTile<20; ++xTile) for (z=0; z<4; ++z) 266 | { 267 | if (isHkRom(rom)) { 268 | tileNum = rom[HELLO_KITTY_FRAME_OFFSETS[frameNumber][1] + xTile + 0x14*z]; 269 | } else { 270 | tileNum = getFrame(rom, frameNumber)[0x600 + xTile + 0x14*z]; 271 | } 272 | 273 | tile = frameAddress + tileNum*16; 274 | x = xTile*8; 275 | y = ((z&1)?8:0) + ((z&2)?128:0); 276 | drawSpan(pixelBuffer, tile, x, y); 277 | } 278 | } 279 | } 280 | 281 | static inline unsigned int interleaveBytes(uint8_t low, uint8_t high) 282 | { 283 | int result; 284 | // We recieve two vars, each 8 bits in length 285 | // We return one int, 16 bits in length, that contains the two vars interleaved 286 | // example: 287 | // low = 00000000 288 | // high = 11111111 289 | // result = 10101010 10101010 290 | 291 | result = low & 1; 292 | result |= (high & 1) << 1; 293 | result |= (low & 2) << 1; 294 | result |= (high & 2) << 2; 295 | result |= (low & 4) << 2; 296 | result |= (high & 4) << 3; 297 | result |= (low & 8) << 3; 298 | result |= (high & 8) << 4; 299 | 300 | result |= (low & 16) << 4; 301 | result |= (high & 16) << 5; 302 | result |= (low & 32) << 5; 303 | result |= (high & 32) << 6; 304 | result |= (low & 64) << 6; 305 | result |= (high & 64) << 7; 306 | result |= (low & 128) << 7; 307 | result |= (high & 128) << 8; 308 | 309 | return result; 310 | } 311 | 312 | void drawSpan(uint8_t pixelBuffer[], uint8_t *buffer, int x, int y) { 313 | unsigned int interleaved; 314 | uint8_t lowBits, highBits; 315 | uint8_t *p, *q; 316 | p = pixelBuffer + (x/4) + y * ROW_SIZE; 317 | for (q = p + ROW_SIZE * 8; p> 8); 324 | } 325 | } 326 | 327 | void writeImageFile(uint8_t pixelBuffer[], const char *filename) 328 | { 329 | int y; 330 | // open file 331 | FILE *fp = fopen(filename, "wb"); 332 | 333 | png_structp png_ptr = png_create_write_struct 334 | (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 335 | 336 | if (!png_ptr) 337 | exit(EXIT_FAILURE); 338 | 339 | png_infop info_ptr = png_create_info_struct(png_ptr); 340 | if (!info_ptr) 341 | { 342 | png_destroy_write_struct(&png_ptr, 343 | (png_infopp)NULL); 344 | exit(EXIT_FAILURE); 345 | } 346 | 347 | // init output 348 | png_init_io( png_ptr, fp ); 349 | png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); 350 | png_set_compression_mem_level(png_ptr, 9); 351 | 352 | // set header info 353 | png_set_IHDR(png_ptr, info_ptr, WIDTH, HEIGHT, 2, 354 | PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); 355 | 356 | // setup row pointers 357 | png_bytep *row_pointers = malloc(sizeof(png_bytep) * HEIGHT); 358 | if (!row_pointers) err(1, "malloc failure"); 359 | for(y=0; y 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | * 19 | * Sponsored in part by the Defense Advanced Research Projects 20 | * Agency (DARPA) and Air Force Research Laboratory, Air Force 21 | * Materiel Command, USAF, under agreement number F39502-99-1-0512. 22 | */ 23 | /*- 24 | * Copyright (c) 2000 The NetBSD Foundation, Inc. 25 | * All rights reserved. 26 | * 27 | * This code is derived from software contributed to The NetBSD Foundation 28 | * by Dieter Baron and Thomas Klausner. 29 | * 30 | * Redistribution and use in source and binary forms, with or without 31 | * modification, are permitted provided that the following conditions 32 | * are met: 33 | * 1. Redistributions of source code must retain the above copyright 34 | * notice, this list of conditions and the following disclaimer. 35 | * 2. Redistributions in binary form must reproduce the above copyright 36 | * notice, this list of conditions and the following disclaimer in the 37 | * documentation and/or other materials provided with the distribution. 38 | * 39 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 40 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 41 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 42 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 43 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 44 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 45 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 46 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 47 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 48 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 49 | * POSSIBILITY OF SUCH DAMAGE. 50 | */ 51 | 52 | #ifdef __MINGW32__ 53 | 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ 63 | 64 | #ifdef REPLACE_GETOPT 65 | int opterr = 1; /* if error message should be printed */ 66 | int optind = 1; /* index into parent argv vector */ 67 | int optopt = '?'; /* character checked for validity */ 68 | #undef optreset /* see getopt.h */ 69 | #define optreset __mingw_optreset 70 | int optreset; /* reset getopt */ 71 | char *optarg; /* argument associated with option */ 72 | #endif 73 | 74 | #define PRINT_ERROR ((opterr) && (*options != ':')) 75 | 76 | #define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ 77 | #define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ 78 | #define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ 79 | 80 | /* return values */ 81 | #define BADCH (int)'?' 82 | #define BADARG ((*options == ':') ? (int)':' : (int)'?') 83 | #define INORDER (int)1 84 | 85 | #ifndef __CYGWIN__ 86 | #define __progname __argv[0] 87 | #else 88 | extern char __declspec(dllimport) *__progname; 89 | #endif 90 | 91 | #ifdef __CYGWIN__ 92 | static char EMSG[] = ""; 93 | #else 94 | #define EMSG "" 95 | #endif 96 | 97 | static int getopt_internal(int, char * const *, const char *, 98 | const struct option *, int *, int); 99 | static int parse_long_options(char * const *, const char *, 100 | const struct option *, int *, int); 101 | static int gcd(int, int); 102 | static void permute_args(int, int, int, char * const *); 103 | 104 | static char *place = EMSG; /* option letter processing */ 105 | 106 | /* XXX: set optreset to 1 rather than these two */ 107 | static int nonopt_start = -1; /* first non option argument (for permute) */ 108 | static int nonopt_end = -1; /* first option after non options (for permute) */ 109 | 110 | /* Error messages */ 111 | static const char recargchar[] = "option requires an argument -- %c"; 112 | static const char recargstring[] = "option requires an argument -- %s"; 113 | static const char ambig[] = "ambiguous option -- %.*s"; 114 | static const char noarg[] = "option doesn't take an argument -- %.*s"; 115 | static const char illoptchar[] = "unknown option -- %c"; 116 | static const char illoptstring[] = "unknown option -- %s"; 117 | 118 | static void 119 | _vwarnx(const char *fmt,va_list ap) 120 | { 121 | (void)fprintf(stderr,"%s: ",__progname); 122 | if (fmt != NULL) 123 | (void)vfprintf(stderr,fmt,ap); 124 | (void)fprintf(stderr,"\n"); 125 | } 126 | 127 | static void 128 | warnx(const char *fmt,...) 129 | { 130 | va_list ap; 131 | va_start(ap,fmt); 132 | _vwarnx(fmt,ap); 133 | va_end(ap); 134 | } 135 | 136 | /* 137 | * Compute the greatest common divisor of a and b. 138 | */ 139 | static int 140 | gcd(int a, int b) 141 | { 142 | int c; 143 | 144 | c = a % b; 145 | while (c != 0) { 146 | a = b; 147 | b = c; 148 | c = a % b; 149 | } 150 | 151 | return (b); 152 | } 153 | 154 | /* 155 | * Exchange the block from nonopt_start to nonopt_end with the block 156 | * from nonopt_end to opt_end (keeping the same order of arguments 157 | * in each block). 158 | */ 159 | static void 160 | permute_args(int panonopt_start, int panonopt_end, int opt_end, 161 | char * const *nargv) 162 | { 163 | int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; 164 | char *swap; 165 | 166 | /* 167 | * compute lengths of blocks and number and size of cycles 168 | */ 169 | nnonopts = panonopt_end - panonopt_start; 170 | nopts = opt_end - panonopt_end; 171 | ncycle = gcd(nnonopts, nopts); 172 | cyclelen = (opt_end - panonopt_start) / ncycle; 173 | 174 | for (i = 0; i < ncycle; i++) { 175 | cstart = panonopt_end+i; 176 | pos = cstart; 177 | for (j = 0; j < cyclelen; j++) { 178 | if (pos >= panonopt_end) 179 | pos -= nnonopts; 180 | else 181 | pos += nopts; 182 | swap = nargv[pos]; 183 | /* LINTED const cast */ 184 | ((char **) nargv)[pos] = nargv[cstart]; 185 | /* LINTED const cast */ 186 | ((char **)nargv)[cstart] = swap; 187 | } 188 | } 189 | } 190 | 191 | /* 192 | * parse_long_options -- 193 | * Parse long options in argc/argv argument vector. 194 | * Returns -1 if short_too is set and the option does not match long_options. 195 | */ 196 | static int 197 | parse_long_options(char * const *nargv, const char *options, 198 | const struct option *long_options, int *idx, int short_too) 199 | { 200 | char *current_argv, *has_equal; 201 | size_t current_argv_len; 202 | int i, ambiguous, match; 203 | 204 | #define IDENTICAL_INTERPRETATION(_x, _y) \ 205 | (long_options[(_x)].has_arg == long_options[(_y)].has_arg && \ 206 | long_options[(_x)].flag == long_options[(_y)].flag && \ 207 | long_options[(_x)].val == long_options[(_y)].val) 208 | 209 | current_argv = place; 210 | match = -1; 211 | ambiguous = 0; 212 | 213 | optind++; 214 | 215 | if ((has_equal = strchr(current_argv, '=')) != NULL) { 216 | /* argument found (--option=arg) */ 217 | current_argv_len = has_equal - current_argv; 218 | has_equal++; 219 | } else 220 | current_argv_len = strlen(current_argv); 221 | 222 | for (i = 0; long_options[i].name; i++) { 223 | /* find matching long option */ 224 | if (strncmp(current_argv, long_options[i].name, 225 | current_argv_len)) 226 | continue; 227 | 228 | if (strlen(long_options[i].name) == current_argv_len) { 229 | /* exact match */ 230 | match = i; 231 | ambiguous = 0; 232 | break; 233 | } 234 | /* 235 | * If this is a known short option, don't allow 236 | * a partial match of a single character. 237 | */ 238 | if (short_too && current_argv_len == 1) 239 | continue; 240 | 241 | if (match == -1) /* partial match */ 242 | match = i; 243 | else if (!IDENTICAL_INTERPRETATION(i, match)) 244 | ambiguous = 1; 245 | } 246 | if (ambiguous) { 247 | /* ambiguous abbreviation */ 248 | if (PRINT_ERROR) 249 | warnx(ambig, (int)current_argv_len, 250 | current_argv); 251 | optopt = 0; 252 | return (BADCH); 253 | } 254 | if (match != -1) { /* option found */ 255 | if (long_options[match].has_arg == no_argument 256 | && has_equal) { 257 | if (PRINT_ERROR) 258 | warnx(noarg, (int)current_argv_len, 259 | current_argv); 260 | /* 261 | * XXX: GNU sets optopt to val regardless of flag 262 | */ 263 | if (long_options[match].flag == NULL) 264 | optopt = long_options[match].val; 265 | else 266 | optopt = 0; 267 | return (BADARG); 268 | } 269 | if (long_options[match].has_arg == required_argument || 270 | long_options[match].has_arg == optional_argument) { 271 | if (has_equal) 272 | optarg = has_equal; 273 | else if (long_options[match].has_arg == 274 | required_argument) { 275 | /* 276 | * optional argument doesn't use next nargv 277 | */ 278 | optarg = nargv[optind++]; 279 | } 280 | } 281 | if ((long_options[match].has_arg == required_argument) 282 | && (optarg == NULL)) { 283 | /* 284 | * Missing argument; leading ':' indicates no error 285 | * should be generated. 286 | */ 287 | if (PRINT_ERROR) 288 | warnx(recargstring, 289 | current_argv); 290 | /* 291 | * XXX: GNU sets optopt to val regardless of flag 292 | */ 293 | if (long_options[match].flag == NULL) 294 | optopt = long_options[match].val; 295 | else 296 | optopt = 0; 297 | --optind; 298 | return (BADARG); 299 | } 300 | } else { /* unknown option */ 301 | if (short_too) { 302 | --optind; 303 | return (-1); 304 | } 305 | if (PRINT_ERROR) 306 | warnx(illoptstring, current_argv); 307 | optopt = 0; 308 | return (BADCH); 309 | } 310 | if (idx) 311 | *idx = match; 312 | if (long_options[match].flag) { 313 | *long_options[match].flag = long_options[match].val; 314 | return (0); 315 | } else 316 | return (long_options[match].val); 317 | #undef IDENTICAL_INTERPRETATION 318 | } 319 | 320 | /* 321 | * getopt_internal -- 322 | * Parse argc/argv argument vector. Called by user level routines. 323 | */ 324 | static int 325 | getopt_internal(int nargc, char * const *nargv, const char *options, 326 | const struct option *long_options, int *idx, int flags) 327 | { 328 | const char *oli; /* option letter list index */ 329 | int optchar, short_too; 330 | static int posixly_correct = -1; 331 | 332 | if (options == NULL) 333 | return (-1); 334 | 335 | /* 336 | * XXX Some GNU programs (like cvs) set optind to 0 instead of 337 | * XXX using optreset. Work around this braindamage. 338 | */ 339 | if (optind == 0) 340 | optind = optreset = 1; 341 | 342 | /* 343 | * Disable GNU extensions if POSIXLY_CORRECT is set or options 344 | * string begins with a '+'. 345 | * 346 | * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or 347 | * optreset != 0 for GNU compatibility. 348 | */ 349 | if (posixly_correct == -1 || optreset != 0) 350 | posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); 351 | if (*options == '-') 352 | flags |= FLAG_ALLARGS; 353 | else if (posixly_correct || *options == '+') 354 | flags &= ~FLAG_PERMUTE; 355 | if (*options == '+' || *options == '-') 356 | options++; 357 | 358 | optarg = NULL; 359 | if (optreset) 360 | nonopt_start = nonopt_end = -1; 361 | start: 362 | if (optreset || !*place) { /* update scanning pointer */ 363 | optreset = 0; 364 | if (optind >= nargc) { /* end of argument vector */ 365 | place = EMSG; 366 | if (nonopt_end != -1) { 367 | /* do permutation, if we have to */ 368 | permute_args(nonopt_start, nonopt_end, 369 | optind, nargv); 370 | optind -= nonopt_end - nonopt_start; 371 | } 372 | else if (nonopt_start != -1) { 373 | /* 374 | * If we skipped non-options, set optind 375 | * to the first of them. 376 | */ 377 | optind = nonopt_start; 378 | } 379 | nonopt_start = nonopt_end = -1; 380 | return (-1); 381 | } 382 | if (*(place = nargv[optind]) != '-' || 383 | (place[1] == '\0' && strchr(options, '-') == NULL)) { 384 | place = EMSG; /* found non-option */ 385 | if (flags & FLAG_ALLARGS) { 386 | /* 387 | * GNU extension: 388 | * return non-option as argument to option 1 389 | */ 390 | optarg = nargv[optind++]; 391 | return (INORDER); 392 | } 393 | if (!(flags & FLAG_PERMUTE)) { 394 | /* 395 | * If no permutation wanted, stop parsing 396 | * at first non-option. 397 | */ 398 | return (-1); 399 | } 400 | /* do permutation */ 401 | if (nonopt_start == -1) 402 | nonopt_start = optind; 403 | else if (nonopt_end != -1) { 404 | permute_args(nonopt_start, nonopt_end, 405 | optind, nargv); 406 | nonopt_start = optind - 407 | (nonopt_end - nonopt_start); 408 | nonopt_end = -1; 409 | } 410 | optind++; 411 | /* process next argument */ 412 | goto start; 413 | } 414 | if (nonopt_start != -1 && nonopt_end == -1) 415 | nonopt_end = optind; 416 | 417 | /* 418 | * If we have "-" do nothing, if "--" we are done. 419 | */ 420 | if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { 421 | optind++; 422 | place = EMSG; 423 | /* 424 | * We found an option (--), so if we skipped 425 | * non-options, we have to permute. 426 | */ 427 | if (nonopt_end != -1) { 428 | permute_args(nonopt_start, nonopt_end, 429 | optind, nargv); 430 | optind -= nonopt_end - nonopt_start; 431 | } 432 | nonopt_start = nonopt_end = -1; 433 | return (-1); 434 | } 435 | } 436 | 437 | /* 438 | * Check long options if: 439 | * 1) we were passed some 440 | * 2) the arg is not just "-" 441 | * 3) either the arg starts with -- we are getopt_long_only() 442 | */ 443 | if (long_options != NULL && place != nargv[optind] && 444 | (*place == '-' || (flags & FLAG_LONGONLY))) { 445 | short_too = 0; 446 | if (*place == '-') 447 | place++; /* --foo long option */ 448 | else if (*place != ':' && strchr(options, *place) != NULL) 449 | short_too = 1; /* could be short option too */ 450 | 451 | optchar = parse_long_options(nargv, options, long_options, 452 | idx, short_too); 453 | if (optchar != -1) { 454 | place = EMSG; 455 | return (optchar); 456 | } 457 | } 458 | 459 | if ((optchar = (int)*place++) == (int)':' || 460 | (optchar == (int)'-' && *place != '\0') || 461 | (oli = strchr(options, optchar)) == NULL) { 462 | /* 463 | * If the user specified "-" and '-' isn't listed in 464 | * options, return -1 (non-option) as per POSIX. 465 | * Otherwise, it is an unknown option character (or ':'). 466 | */ 467 | if (optchar == (int)'-' && *place == '\0') 468 | return (-1); 469 | if (!*place) 470 | ++optind; 471 | if (PRINT_ERROR) 472 | warnx(illoptchar, optchar); 473 | optopt = optchar; 474 | return (BADCH); 475 | } 476 | if (long_options != NULL && optchar == 'W' && oli[1] == ';') { 477 | /* -W long-option */ 478 | if (*place) /* no space */ 479 | /* NOTHING */; 480 | else if (++optind >= nargc) { /* no arg */ 481 | place = EMSG; 482 | if (PRINT_ERROR) 483 | warnx(recargchar, optchar); 484 | optopt = optchar; 485 | return (BADARG); 486 | } else /* white space */ 487 | place = nargv[optind]; 488 | optchar = parse_long_options(nargv, options, long_options, 489 | idx, 0); 490 | place = EMSG; 491 | return (optchar); 492 | } 493 | if (*++oli != ':') { /* doesn't take argument */ 494 | if (!*place) 495 | ++optind; 496 | } else { /* takes (optional) argument */ 497 | optarg = NULL; 498 | if (*place) /* no white space */ 499 | optarg = place; 500 | else if (oli[1] != ':') { /* arg not optional */ 501 | if (++optind >= nargc) { /* no arg */ 502 | place = EMSG; 503 | if (PRINT_ERROR) 504 | warnx(recargchar, optchar); 505 | optopt = optchar; 506 | return (BADARG); 507 | } else 508 | optarg = nargv[optind]; 509 | } 510 | place = EMSG; 511 | ++optind; 512 | } 513 | /* dump back option letter */ 514 | return (optchar); 515 | } 516 | 517 | #ifdef REPLACE_GETOPT 518 | /* 519 | * getopt -- 520 | * Parse argc/argv argument vector. 521 | * 522 | * [eventually this will replace the BSD getopt] 523 | */ 524 | int 525 | getopt(int nargc, char * const *nargv, const char *options) 526 | { 527 | 528 | /* 529 | * We don't pass FLAG_PERMUTE to getopt_internal() since 530 | * the BSD getopt(3) (unlike GNU) has never done this. 531 | * 532 | * Furthermore, since many privileged programs call getopt() 533 | * before dropping privileges it makes sense to keep things 534 | * as simple (and bug-free) as possible. 535 | */ 536 | return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); 537 | } 538 | #endif /* REPLACE_GETOPT */ 539 | 540 | /* 541 | * getopt_long -- 542 | * Parse argc/argv argument vector. 543 | */ 544 | int 545 | getopt_long(int nargc, char * const *nargv, const char *options, 546 | const struct option *long_options, int *idx) 547 | { 548 | 549 | return (getopt_internal(nargc, nargv, options, long_options, idx, 550 | FLAG_PERMUTE)); 551 | } 552 | 553 | /* 554 | * getopt_long_only -- 555 | * Parse argc/argv argument vector. 556 | */ 557 | int 558 | getopt_long_only(int nargc, char * const *nargv, const char *options, 559 | const struct option *long_options, int *idx) 560 | { 561 | 562 | return (getopt_internal(nargc, nargv, options, long_options, idx, 563 | FLAG_PERMUTE|FLAG_LONGONLY)); 564 | } 565 | 566 | #endif 567 | --------------------------------------------------------------------------------