text: /*
   * Manage cheat codes
   *
   * Copyright (C) 2009-2010 Mathias Lafeldt <misfire@debugon.org>
   * Copyright (C) 2014 doctorxyz
   *
   * This file is part of PS2rd, the PS2 remote debugger.
   *
   * PS2rd is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * PS2rd is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with PS2rd.  If not, see <http://www.gnu.org/licenses/>.
   *
   * $Id$
   */
  
  #include "include/cheatman.h"
  #include "include/ioman.h"
  
  /*
   * make_code - Return a code object from string @s.
   */
  static code_t make_code(const char *s)
  {
  	code_t code;
  	int address;
  	int value;
  	char digits[CODE_DIGITS];
  	int i = 0;
  
  	while (*s) {
  		if (isxdigit(*s))
  			digits[i++] = *s;
  		s++;
  	}
  
  	sscanf(digits, "%08X %08X", &address, &value);
  
  	//Return Code Address and Value
  	code.addr = address;
  	code.val = value;
  	return code;
  }
  
  
  /*
   * is_cheat_code - Return non-zero if @s indicates a cheat code.
   *
   * Example: 10B8DAFA 00003F00
   */
  static int is_cheat_code(const char *s)
  {
  	int i = 0;
  
  	while (*s) {
  		if (isxdigit(*s)) {
  			if (++i > CODE_DIGITS)
  				return 0;
  		}
  		else if (!isspace(*s)) {
  			return 0;
  		}
  		s++;
  	}
  
  	return (i == CODE_DIGITS);
  }
  
  /*
   * parse_line - Parse the current line and return a filled (code found) or zeroed (code not found) object.
   */
  static code_t parse_line(const char *line, int linenumber)
  {
  	code_t code;
  	code.addr = 0;
  	code.val = 0;
  	int ret;
  	LOG("%4i  %s\n", linenumber, line);
  	ret = is_cheat_code(line);
  	if (ret){
  		/* Process actual code and add it to the list. */
  		code = make_code(line);
  	}
  	return code;
  }
  
  /*
   * is_cmt_str - Return non-zero if @s indicates a comment.
   */
  static inline int is_cmt_str(const char *s)
  {
  	return (strlen(s) >= 2 && !strncmp(s, "//", 2)) || (*s == '#');
  }
  
  /*
   * chr_idx - Returns the index within @s of the first occurrence of the
   * specified char @c.  If no such char occurs in @s, then (-1) is returned.
   */
  size_t chr_idx(const char *s, char c)
  {
  	size_t i = 0;
  
  	while (s[i] && (s[i] != c))
  		i++;
  
  	return (s[i] == c) ? i : -1;
  }
  
  /*
   * term_str - Terminate string @s where the callback functions returns non-zero.
   */
  char *term_str(char *s, int(*callback)(const char *))
  {
  	if (callback != NULL) {
  		while (*s) {
  			if (callback(s)) {
  				*s = NUL;
  				break;
  			}
  			s++;
  		}
  	}
  
  	return s;
  }
  
  /*
   * is_empty_str - Returns 1 if @s contains no printable chars other than white
   * space.  Otherwise, 0 is returned.
   */
  int is_empty_str(const char *s)
  {
  	size_t slen = strlen(s);
  
  	while (slen--) {
  		if (isgraph(*s++))
  			return 0;
  	}
  
  	return 1;
  }
  
  /*
   * trim_str - Removes white space from both ends of the string @s.
   */
  int trim_str(char *s)
  {
  	size_t first = 0;
  	size_t last;
  	size_t slen;
  	char *t = s;
  
  	/* Return if string is empty */
  	if (is_empty_str(s))
  		return -1;
  
  	/* Get first non-space char */
  	while (isspace(*t++))
  		first++;
  
  	/* Get last non-space char */
  	last = strlen(s) - 1;
  	t = &s[last];
  	while (isspace(*t--))
  		last--;
  
  	/* Kill leading/trailing spaces */
  	slen = last - first + 1;
  	memmove(s, s + first, slen);
  	s[slen] = NUL;
  
  	return slen;
  }
  
  /*
   * is_empty_substr - Returns 1 if the first @count chars of @s are not printable
   * (apart from white space).  Otherwise, 0 is returned.
   */
  int is_empty_substr(const char *s, size_t count)
  {
  	while (count--) {
  		if (isgraph(*s++))
  			return 0;
  	}
  
  	return 1;
  }
  
  /* Max line length to parse */
  #define LINE_MAX	255
  
  /**
   * parse_buf - Parse a text buffer for cheats.
   * @buf: buffer holding text (must be NUL-terminated!)
   * @return: 0: success, -1: error
   */
  int parse_buf(const char *buf)
  {
  	code_t code;
  	char line[LINE_MAX + 1];
  	int linenumber = 1;
  
  	if (buf == NULL) return -1;
  
  	int i = 0;
  	
  	while (*buf) {
  		/* Scanner */
  		int len = chr_idx(buf, LF);
  		if (len < 0)
  			len = strlen(line);
  		else if (len > LINE_MAX)
  			len = LINE_MAX;
  
  		if (!is_empty_substr(buf, len)) {
  			strncpy(line, buf, len);
  			line[len] = NUL;
  
  			/* Screener */
  			term_str(line, is_cmt_str);
  			trim_str(line);
  
  			/* Parser */
  			code = parse_line(line, linenumber);
  			if (!((code.addr == 0) && (code.val == 0))) {
  				gCheatList[i] = code.addr;
  				i++;
  				gCheatList[i] = code.val;
  				i++;
  			}
  		}
  		linenumber++;
  		buf += len + 1;
  	}
  
  	gCheatList[i] = 0;
  	i++;
  	gCheatList[i] = 0;
  	i++;
  
  	return 0;
  }
  
  /**
   * read_text_file - Reads text from a file into a buffer.
   * @filename: name of text file
   * @maxsize: max file size (0: arbitrary size)
   * @return: ptr to NULL-terminated text buffer, or NULL if an error occured
   */
  static inline char *read_text_file(const char *filename, int maxsize)
  {
  	char *buf = NULL;
  	int fd, filesize;
  
  	fd = open(filename, O_RDONLY);
  	if (fd < 0) {
  		LOG("%s: Can't open text file %s\n", __FUNCTION__, filename);
  		return NULL;
  	}
  
  	filesize = lseek(fd, 0, SEEK_END);
  	if (maxsize && filesize > maxsize) {
  		LOG("%s: Text file too large: %i bytes, max: %i bytes\n", __FUNCTION__, filesize, maxsize);
  		goto end;
  	}
  
  	buf = malloc(filesize + 1);
  	if (buf == NULL) {
  		LOG("%s: Unable to allocate %i bytes\n", __FUNCTION__, filesize + 1);
  		goto end;
  	}
  
  	if (filesize > 0) {
  		lseek(fd, 0, SEEK_SET);
  		if (read(fd, buf, filesize) != filesize) {
  			LOG("%s: Can't read from text file %s\n", __FUNCTION__, filename);
  			free(buf);
  			buf = NULL;
  			goto end;
  		}
  	}
  
  	buf[filesize] = '\0';
  end:
  	close(fd);
  	return buf;
  }
  
  /*
   * Load cheats from text file.
   */
  int load_cheats(const char *cheatfile)
  {
  	char *buf = NULL;
  	int ret;
  	LOG("%s: Reading cheat file '%s'...", __FUNCTION__, cheatfile);
  	buf = read_text_file(cheatfile, 0);
  	if (buf == NULL) {
  		LOG("\n%s: Could not read cheats file '%s'\n", __FUNCTION__, cheatfile);
  		return -1;
  	}
  	LOG("Ok!\n");
  	ret = parse_buf(buf);
  	free(buf);
  	if (ret < 0)
  		return -1;
  	else
  		return 0;
  }
  