Create a gist now

Instantly share code, notes, and snippets.

@Cloudef /loligrab.c
Last active Dec 19, 2015

What would you like to do?
Grab text from 空の軌跡FC and 空の軌跡SC
/* gcc -g loligrab.c -o loligrab */
/* for pread */
#ifdef _XOPEN_SOURCE
# undef _XOPEN_SOURCE
#endif
#define _XOPEN_SOURCE 500
/* for DT_DIR */
#ifndef _BSD_SOURCE
# define _BSD_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <signal.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/ptrace.h>
#include <iconv.h>
/* process we launch */
static pid_t CHILDPID = 0;
static pid_t CCHILDPID = 0; /* childrenmost PID */
static int NAMEPIPE = -1;
static int MSGPIPE = -1;
static const char *NAMEPIPE_PATH = "/tmp/loligrab0";
static const char *MSGPIPE_PATH = "/tmp/loligrab1";
/* ---- ptrace functions from scanmem ---- */
/* Dirty hack for FreeBSD */
#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
# define PTRACE_ATTACH PT_ATTACH
# define PTRACE_DETACH PT_DETACH
# define PTRACE_PEEKDATA PT_READ_D
# define PTRACE_POKEDATA PT_WRITE_D
#endif
#ifdef __GNUC__
# define EXPECT(x,y) __builtin_expect(x, y)
#else
# define EXPECT(x,y) x
#endif
/* ptrace peek buffer, used by peekdata() */
/* make it larger in order to reduce shift */
/* #define MAX_PEEKBUF_SIZE (4*sizeof(int64_t)) */
#define MAX_PEEKBUF_SIZE (1024)
static struct {
char cache[MAX_PEEKBUF_SIZE]; /* read from ptrace() */
unsigned size; /* number of entries (in bytes) */
char *base; /* base address of cached region */
pid_t pid; /* what pid this applies to */
} peekbuf;
static int attach(pid_t target)
{
int status;
/* attach, to the target application, which should cause a SIGSTOP */
if (ptrace(PTRACE_ATTACH, target, NULL, NULL) == -1L) {
printf("-!- failed to attach to %d, %s\n", target, strerror(errno));
return 0;
}
/* wait for the SIGSTOP to take place. */
if (waitpid(target, &status, 0) == -1 || !WIFSTOPPED(status)) {
printf("-!- there was an error waiting for the target to stop.\n");
printf("%s\n", strerror(errno));
return 0;
}
/* flush the peek buffer */
memset(&peekbuf, 0x00, sizeof(peekbuf));
/* everything looks okay */
return 1;
}
static int detach(pid_t target)
{
// addr is ignore under Linux, but should be 1 under FreeBSD in order to let the child process continue at what it had been interrupted
return ptrace(PTRACE_DETACH, target, 1, 0) == 0;
}
/* read region using /proc/pid/mem */
static ssize_t readregion(pid_t target, void *buf, size_t count, unsigned long offset)
{
char mem[32];
int fd;
ssize_t len;
/* print the path to mem file */
snprintf(mem, sizeof(mem), "/proc/%d/mem", target);
/* attempt to open the file */
if ((fd = open(mem, O_RDONLY)) == -1) {
printf("-!- unable to open %s.\n", mem);
return -1;
}
/* try to honour the request */
len = pread(fd, buf, count, offset);
/* clean up */
close(fd);
return len;
}
static int read_array(pid_t target, void *addr, char *buf, int len)
{
if (!attach(target)) return 0;
#if HAVE_PROCMEM
unsigned nread=0;
ssize_t tmpl;
while (nread < len) {
if ((tmpl = readregion(target, buf+nread, len-nread, (unsigned long)(addr+nread))) == -1) {
/* no, continue with whatever data was read */
break;
} else {
/* some data was read */
nread += tmpl;
}
}
if (nread < len) {
detach(target);
return 0;
}
return detach(target);
#else
int i;
/* here we just read long by long, this should be ok for most of time */
/* partial hit is not handled */
for (i = 0; i < len; i += sizeof(long)) {
errno = 0;
*((long *)(buf+i)) = ptrace(PTRACE_PEEKDATA, target, addr+i, NULL);
if (EXPECT((*((long *)(buf+i)) == -1L) && (errno != 0), 0)) {
detach(target);
return 0;
}
}
return detach(target);
#endif
}
/* ^^^^ ptrace functions from scanmem ^^^^ */
static pid_t find_child_pid(pid_t ppid, int recursive)
{
DIR *dp;
pid_t cpid = ppid;
int crawl_child_pid = 1;
while (crawl_child_pid) {
crawl_child_pid = 0;
if ((dp = opendir("/proc"))) {
struct dirent *ep;
while ((ep = readdir(dp))) {
if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, "..")) continue;
if (ep->d_type != DT_DIR) continue;
pid_t pid = 0;
sscanf(ep->d_name, "%u", &pid);
if (pid <= cpid) continue;
FILE *f;
char path[1024];
snprintf(path, sizeof(path)-1, "/proc/%s/status", ep->d_name);
if (!(f = fopen(path, "r"))) continue;
char buf[1024];
memset(buf, 0, sizeof(buf));
while (strncmp(buf, "PPid:", strlen("PPid:")) && fgets(buf, sizeof(buf)-1, f) != NULL);
fclose(f);
pid_t ppid = 0;
sscanf(buf, "%*s%u", &ppid);
if (ppid != cpid) continue;
cpid = pid;
if (recursive) crawl_child_pid = 1;
else break;
}
closedir(dp);
}
}
return cpid;
}
static char* convert_sjis_to_utf8(const char *sjis)
{
const char *INSET = "Shift_JIS";
const char *OUTSET = "UTF-8";
iconv_t icd;
size_t iret;
char *p_src, *p_dst, *p_start;
size_t n_src, n_dst;
p_src = (char*)sjis;
n_src = strlen(p_src);
n_dst = n_src * 2;
if (!p_src || !n_src) return NULL;
if (!(p_dst = calloc(n_dst, 1))) return NULL;
if (!(icd = iconv_open(OUTSET, INSET))) {
if(errno == EINVAL) {
printf("ICONV :: Conversion from '%s' to '%s' is not supported.\n", INSET, OUTSET);
} else {
printf("ICONV :: Initialization failure: %s\n", strerror(errno));
}
return NULL;
}
p_start = p_dst;
iret = iconv(icd, &p_src, &n_src, &p_dst, &n_dst);
if (iret == (size_t)-1) {
#if 0
printf("ICONV :: Conversion failure.\n");
switch (errno) {
/* See "man 3 iconv" for an explanation. */
case EILSEQ:
printf("Invalid multibyte sequence.\n");
break;
case EINVAL:
printf("Incomplete multibyte sequence.\n");
break;
case E2BIG:
printf("No more room.\n");
break;
default:
printf("%s\n", strerror(errno));
}
#endif
free(p_start);
return NULL;
}
iconv_close(icd);
return p_start;
}
/* usage */
static void usage(const char *name)
{
printf("usage: %s PROGRAM [ARGUMENTS...]\n", name);
printf(" %s --help\n", name);
printf(" %s --version\n", name);
}
/* version */
static void version(const char *name)
{
printf("%s-1.0\n", name);
}
/* kill pipes */
static void sigpipe(int sig)
{
if (NAMEPIPE != -1) {
close(NAMEPIPE);
remove(NAMEPIPE_PATH);
MSGPIPE = -1;
}
if (MSGPIPE != -1) {
close(MSGPIPE);
remove(MSGPIPE_PATH);
MSGPIPE = -1;
}
}
/* create named pipe */
static void mknamepipe(void)
{
if (NAMEPIPE != -1) return;
mkfifo(NAMEPIPE_PATH, 0777);
NAMEPIPE = open(NAMEPIPE_PATH, O_WRONLY|O_NONBLOCK);
}
static void mkmsgpipe(void)
{
if (MSGPIPE != -1) return;
mkfifo(MSGPIPE_PATH, 0777);
MSGPIPE = open(MSGPIPE_PATH, O_WRONLY|O_NONBLOCK);
}
/* program termination */
static void terminate(void)
{
sigpipe(SIGPIPE);
if (CHILDPID) kill(CHILDPID, SIGTERM);
}
/* sigint/sigquit handler */
static void sigint(int sig)
{
printf("-!- SIGINT\n");
terminate();
exit(sig);
}
int utf8_get_char_length(const unsigned char *str)
{
if (!((*str & 0xC0) ^ 0x80))
return 0;
if (!(*str & 0x80))
return 1;
if (!((*str & 0xE0) ^ 0xC0))
return 2;
if (!((*str & 0xF0) ^ 0xE0))
return 3;
if (!((*str & 0xF8) ^ 0xF0))
return 4;
return 0;
}
int utf8_validate(const unsigned char *str)
{
int i, j;
j = utf8_get_char_length(str);
if (j == 0) return -1;
for (i = 1; i < j; i++)
if (((str[i] & 0xC0) ^ 0x80))
return -1;
if (str[0] == 0xC0 || str[i] == 0xC1)
return -1;
if (str[0] >= 0xF5)
return -1;
return 0;
}
/* lolis live here */
int main(int argc, char *const argv[])
{
static int startArg = 1;
/* output the usage */
if (argc-startArg < 1 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) {
usage(basename((char*)argv[0]));
return EXIT_SUCCESS;
}
/* output the version */
if (!strcmp(argv[1], "--version") || !strcmp(argv[1], "-v")) {
version(basename((char*)argv[0]));
return EXIT_SUCCESS;
}
/* try to fork */
if ((CCHILDPID = CHILDPID = fork()) == -1)
return EXIT_FAILURE;
/* launch our program and inject lolis */
if (CHILDPID == 0) {
execvp(argv[startArg], &argv[startArg]);
_exit(EXIT_SUCCESS);
}
/* wait for game to launch */
sleep(2);
/* find the childrenmost PID */
CCHILDPID = find_child_pid(CCHILDPID, 1);
/* setup signal handler */
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sigint;
sigaction(SIGINT, &act, NULL);
sigaction(SIGQUIT, &act, NULL);
struct sigaction act2;
memset(&act2, 0, sizeof(act2));
act2.sa_handler = sigpipe;
sigaction(SIGPIPE, &act2, NULL);
/* setup buffers */
char *s, *ns;
char buf[1024];
char nbuf[sizeof(buf)];
/* name, message */
const long noname_xx = 0xDEADBEEF;
void *ED6FC[] = {(void*)0x17a3d94, (void*)0x17a3994, /* FC */
(void*)0x17a8b74, (void*)0x17a8774,
(void*)0x17f1b94, (void*)0x17f1794,
NULL};
void *ED6SC[] = {(void*)0x2d8c794, (void*)0x2d8c394, /* SC */
(void*)0x2ca68e4, (void*)0x2d91174,
(void*)noname_xx, (void*)0x2dda194,
NULL};
/* game detection */
void *detect[] = {"空の軌跡FC", (void*)0x192b917, "FALCOM\\ED6", ED6FC,
"空の軌跡SC", (void*)0x2f116ef, "FALCOM\\ED_SORA2", ED6SC,
NULL};
int i, i2;
void **addrs = NULL;
for (i = 0; detect[i]; i += 4) {
memset(buf, 0, sizeof(buf));
if (!read_array(CCHILDPID, detect[i+1], buf, strlen(detect[i+2]))) {
terminate();
return EXIT_FAILURE;
}
if (!strncmp(buf, detect[i+2], strlen(detect[i+2]))) {
addrs = detect[i+3];
printf("-!- DETECTED: %s\n", detect[i]);
break;
}
}
/* check if game was found */
if (!addrs) {
printf("-!- WARNING: No game detected.\n");
} else {
/* ready pipes */
mknamepipe();
mkmsgpipe();
}
printf("\n");
/* start grabbing lolis */
for (i = 0; addrs && addrs[i]; ++i);
char *obufs[i];
memset(obufs, 0, i*sizeof(char*));
while (1) {
for (i = 0; addrs && addrs[i]; ++i) {
if (addrs[i] == (void*)noname_xx) continue;
memset(buf, 0, sizeof(buf));
memset(nbuf, 0, sizeof(nbuf));
if (!read_array(CCHILDPID, addrs[i], buf, sizeof(buf))) {
terminate();
return EXIT_FAILURE;
}
if (buf[0] == 0) continue;
/* holds copy when source isn't utf8 */
// printf("ORIG: %s\n", buf);
char *utf8 = convert_sjis_to_utf8(buf);
// printf("UTF8: %s\n", (utf8?utf8:buf));
/* parse message */
for (s = (utf8?utf8:buf), ns = nbuf; *s; ++s) {
if (*s == '#') for (; isdigit(*s) || (isalpha(*s) && isupper(*s)) || *s == '#'; ++s);
if (*s == '\01') *s = ' ';
if (*s == '\02') *s = '\n';
if (*s == '\03') continue;
if (s && utf8_validate(s) == 0) {
memcpy(ns, s, utf8_get_char_length(s));
ns += utf8_get_char_length(s);
}
}
/* we don't need this buffer anymore */
if (utf8) free(utf8);
/* check if old buffer is same as new and bail out */
if (obufs[i] && !strcmp(obufs[i], nbuf)) continue;
/* store new buffer */
if (nbuf[0] != 0) {
if (obufs[i]) free(obufs[i]);
obufs[i] = strdup(nbuf);
}
/* print new buffers (name, message) */
if (nbuf[0] != 0 && (i % 2)) {
if (obufs[i-1] && obufs[i-1] != 0) {
if (NAMEPIPE == -1) mknamepipe();
if (NAMEPIPE != -1) {
write(NAMEPIPE, obufs[i-1], strlen(obufs[i-1]));
write(NAMEPIPE, "\n", 1);
}
printf("%s\n", obufs[i-1]);
for (i2 = 0; i2 < strlen(obufs[i-1]); ++i2) printf("-");
printf("\n");
}
if (MSGPIPE == -1) mkmsgpipe();
if (MSGPIPE != -1) {
write(MSGPIPE, nbuf, strlen(nbuf));
write(MSGPIPE, "\n", 1);
}
printf("%s\n", nbuf);
}
}
usleep(50000);
}
/* terminate and exit */
terminate();
return EXIT_SUCCESS;
}
/* vim: set ts=8 sw=3 tw=0 :*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment