shithub: pse

Download patch

ref: b492d42e2545b8c414c917f7544bdd4b92e406e8
parent: 5e0dae801ca8b4d79a1fa08667b42f5457005e73
author: Jacob Moody <[email protected]>
date: Fri May 19 22:39:14 EDT 2023

colo support, large refactor for colo support

I dont love the View function pointer stuff.
its whatever

--- /dev/null
+++ b/colo.c
@@ -1,0 +1,155 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include "colodat.h"
+#include "gen3dat.h"
+#include "pse.h"
+
+#define GET2(p) (u16int)(p)[1] | (u16int)(p)[0]<<8
+#define GET4(p) (u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
+
+static void
+dodecrypt(uchar *dst, uchar *src)
+{
+	uchar d1[SHA1dlen];
+	uchar d2[SHA1dlen];
+	uchar *s, *e, *dot;
+	int i;
+
+	memcpy(d2, src + Slotszcolo - SHA1dlen, SHA1dlen);
+	for(i = 0; i < SHA1dlen; i++)
+		d2[i] = ~d2[i];
+
+	s = src + 0x18;
+	dst += 0x18;
+	e = src + Slotszcolo - (2*SHA1dlen);
+	while(s < e){
+		dot = s + SHA1dlen;
+		if(dot > e)
+			dot = e;
+		sha1(s, dot - s, d1, nil);
+		for(i = 0; i < dot - s; i++)
+			*dst++ = s[i] ^ d2[i];
+		memmove(d2, d1, dot - s);
+		s = dot;
+	}
+}
+
+void
+getcolo(int fd, Colo *dst)
+{
+	uchar buf[0x6000];
+	int i;
+	uchar *dd;
+	u32int max = 0;
+
+	if(readn(fd, dst->gcihdr, sizeof dst->gcihdr) != sizeof dst->gcihdr)
+		sysfatal("read: %r");
+	if(readn(fd, buf, sizeof buf) != sizeof buf)
+		sysfatal("read: %r");
+	for(i = 0; i < nelem(dst->slots); i++)
+		if(readn(fd, dst->slots[i], sizeof dst->slots[i]) != sizeof dst->slots[i])
+			sysfatal("read: %r");
+	for(i = 0; i < nelem(dst->index); i++){
+		dst->index[i] = GET4(&dst->slots[i][4]);
+		if(dst->index[i] > max)
+			dst->active = dst->slots[i];
+	}
+	dodecrypt(dst->decrypted, dst->active);
+	gettrainercolo(&dst->tr, dst->decrypted+0x78);
+	dst->money = GET4(dst->decrypted+0xAFC);
+	dst->coupons = GET4(dst->decrypted+0xB00);
+	dd = dst->decrypted + 0x00B90;
+	for(i = 0; i < 30*3; i++){
+		getpokemoncolo(dst->pc+i, dd + ((i/30 + 1) * 0x14));
+		dd += 312;
+	}
+}
+
+#pragma varargck type "L" uchar*
+
+/* colo strings are UTF16 but the game predates surrogate pairs */
+static int
+colostrfmt(Fmt *f)
+{
+	uchar *p;
+	Rune r;
+	int n;
+
+	p = va_arg(f->args, uchar*);
+	for(n = 0;; p +=2){
+		r = GET2(p);
+		if(r == 0)
+			break;
+		n += fmtprint(f, "%C", r);
+	}
+	return n;
+}
+
+static void
+vinit(void)
+{
+	fmtinstall('L', colostrfmt);
+}
+
+extern int gen3speciestab[];
+extern char *dexfiletab[];
+extern char *movenametab[];
+
+static int
+vdex(void *v)
+{
+	Pokemoncolo *p;
+
+	p = v;
+	if(p->species == 0)
+		return -1;
+	return gen3speciestab[p->species]-1;
+}
+
+static int
+vhdr(char *dst, char *e, void *v, int box)
+{
+	Colo *s;
+
+	s = v;
+	dst = seprint(dst, e, "Name: %L  ID: %d  Secret ID: %d\n", s->tr.name, s->tr.id, s->tr.secretid);
+	dst = seprint(dst, e, "Game: Colosseum  Money: %ud  Coupons: %ud\n", s->money, s->coupons);
+	seprint(dst, e, "Box %d: %L\n", box+1, s->decrypted + 0x00B90 + box*0x24a4);
+	return 0;
+}
+
+static int
+vbody(char *dst, char *e, void *v)
+{
+	Pokemoncolo *p;
+
+	p = v;
+	dst = seprint(dst, e, "Name: %L\n", p->name);
+	dst = seprint(dst, e, "OT Name: %L  OT ID: %ud  OT Secret ID: %d\n", p->otname, p->otid, p->otsecretid);
+	dst = seprint(dst, e, "National Dex: %d\n", gen3speciestab[p->species]-1);
+	dst = seprint(dst, e, "Exp: %d\n", p->exp);
+	dst = seprint(dst, e, "Move 1: %s  Move 2: %s\n", movenametab[p->moves[0].id], movenametab[p->moves[1].id]);
+	dst = seprint(dst, e, "Move 3: %s  Move 4: %s\n", movenametab[p->moves[2].id], movenametab[p->moves[3].id]);
+	dst = seprint(dst, e, "[EV] HP: %d  Atk: %d  Def: %d  SpA: %d  SpD: %d  Spe: %d\n", p->ev.hp, p->ev.atk, p->ev.def, p->ev.spa, p->ev.spd, p->ev.spe);
+	seprint(dst, e, "[IV] HP: %d  Atk: %d  Def: %d  SpA: %d  SpD: %d  Spe: %d\n", p->iv.hp, p->iv.atk, p->iv.def, p->iv.spa, p->iv.spd, p->iv.spe);
+	return 0;
+}
+
+static void*
+vbox(int box, int i, void *v)
+{
+	Colo *c;
+
+	c = v;
+	return c->pc + box*30 + i;
+}
+
+View vcolo = {
+	vinit,
+	vhdr,
+	vdex,
+	vbody,
+	vbox,
+};
--- /dev/null
+++ b/colodat.c
@@ -1,0 +1,303 @@
+#include <u.h>
+#include <libc.h>
+#include "colodat.h"
+
+#define GET2(p) (u16int)(p)[1] | (u16int)(p)[0]<<8
+#define PUT2(p, u) (p)[0] = (u)>>8, (p)[1] = (u)
+#define GET3(p) (u32int)(p)[2] | (u32int)(p)[1]<<8 | (u32int)(p)[0]<<16
+#define PUT3(p, u) (p)[0] = (u)>>16, (p)[1] = (u)>>8, (p)[2] = (u)
+#define GET4(p) (u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
+#define PUT4(p, u) (p)[0] = (u)>>24, (p)[1] = (u)>>16, (p)[2] = (u)>>8, (p)[3] = (u)
+#define GET5(p) (u64int)(p)[4] | (u64int)(p)[3]<<8 | (u64int)(p)[2]<<16 | (u64int)(p)[1]<<24 | (u64int)(p)[0]<<32
+#define PUT5(p, u) (p)[0] = (u)>>32, (p)[1] = (u)>>24, (p)[2] = (u)>>16, (p)[3] = (u)>>8, (p)[4] = (u)
+#define GET6(p) (u64int)(p)[5] | (u64int)(p)[4]<<8 | (u64int)(p)[3]<<16 | (u64int)(p)[2]<<24 | (u64int)(p)[1]<<32 | (u64int)(p)[0]<<40
+#define PUT6(p, u) (p)[0] = (u)>>40, (p)[1] = (u)>>32, (p)[2] = (u)>>24, (p)[3] = (u)>>16, (p)[4] = (u)>>8, (p)[5] = (u)
+#define GET7(p) (u64int)(p)[6] | (u64int)(p)[5]<<8 | (u64int)(p)[4]<<16 | (u64int)(p)[3]<<24 | (u64int)(p)[2]<<32 | (u64int)(p)[1]<<40 | (u64int)(p)[0]<<48
+#define PUT7(p, u) (p)[0] = (u)>>48, (p)[1] = (u)>>40, (p)[2] = (u)>>32, (p)[3] = (u)>>24, (p)[4] = (u)>>16, (p)[5] = (u)>>8, (p)[6] = (u)
+#define GET8(p) (u64int)(p)[7] | (u64int)(p)[6]<<8 | (u64int)(p)[5]<<16 | (u64int)(p)[4]<<24 | (u64int)(p)[3]<<32 | (u64int)(p)[2]<<40 | (u64int)(p)[1]<<48 | (u64int)(p)[0]<<56
+#define PUT8(p, u) (p)[0] = (u)>>56, (p)[1] = (u)>>48, (p)[2] = (u)>>40, (p)[3] = (u)>>32, (p)[4] = (u)>>24, (p)[5] = (u)>>16, (p)[6] = (u)>>8, (p)[7] = (u)
+
+long
+getmovecolo(Movecolo *ret, uchar *data)
+{
+	long n;
+
+	n = 0;
+	ret->id = GET2(data+n);
+	n += 2;
+	ret->pp = data[n];
+	n += 1;
+	ret->up = data[n];
+	n += 1;
+	return n;
+}
+
+long
+putmovecolo(uchar *dst, Movecolo *src)
+{
+	long n;
+
+	n = 0;
+	PUT2(dst+n, src->id);
+	n += 2;
+	dst[n] = src->pp;
+	n += 1;
+	dst[n] = src->up;
+	n += 1;
+	return n;
+}
+
+long
+getstatcolo(Statcolo *ret, uchar *data)
+{
+	long n;
+
+	n = 0;
+	ret->hp = GET2(data+n);
+	n += 2;
+	ret->atk = GET2(data+n);
+	n += 2;
+	ret->def = GET2(data+n);
+	n += 2;
+	ret->spa = GET2(data+n);
+	n += 2;
+	ret->spd = GET2(data+n);
+	n += 2;
+	ret->spe = GET2(data+n);
+	n += 2;
+	return n;
+}
+
+long
+putstatcolo(uchar *dst, Statcolo *src)
+{
+	long n;
+
+	n = 0;
+	PUT2(dst+n, src->hp);
+	n += 2;
+	PUT2(dst+n, src->atk);
+	n += 2;
+	PUT2(dst+n, src->def);
+	n += 2;
+	PUT2(dst+n, src->spa);
+	n += 2;
+	PUT2(dst+n, src->spd);
+	n += 2;
+	PUT2(dst+n, src->spe);
+	n += 2;
+	return n;
+}
+
+long
+getpokemoncolo(Pokemoncolo *ret, uchar *data)
+{
+	long n;
+
+	n = 0;
+	ret->species = GET2(data+n);
+	n += 2;
+	ret->pad0 = GET2(data+n);
+	n += 2;
+	ret->pid = GET4(data+n);
+	n += 4;
+	ret->version = data[n];
+	n += 1;
+	ret->curregion = data[n];
+	n += 1;
+	ret->oriregion = data[n];
+	n += 1;
+	ret->lang = data[n];
+	n += 1;
+	ret->metloc = GET2(data+n);
+	n += 2;
+	ret->metlvl = data[n];
+	n += 1;
+	ret->ball = data[n];
+	n += 1;
+	ret->otgender = data[n];
+	n += 1;
+	memcpy(ret->pad3, data+n, 3);
+	n += 3;
+	ret->otid = GET2(data+n);
+	n += 2;
+	ret->otsecretid = GET2(data+n);
+	n += 2;
+	memcpy(ret->otname, data+n, 22);
+	n += 22;
+	memcpy(ret->name, data+n, 22);
+	n += 22;
+	memcpy(ret->namecopy, data+n, 22);
+	n += 22;
+	ret->pad1 = GET2(data+n);
+	n += 2;
+	ret->exp = GET4(data+n);
+	n += 4;
+	ret->statlvl = data[n];
+	n += 1;
+	memcpy(ret->battle, data+n, 23);
+	n += 23;
+	n += getmovecolo(&ret->moves[0], data+n);
+	n += getmovecolo(&ret->moves[1], data+n);
+	n += getmovecolo(&ret->moves[2], data+n);
+	n += getmovecolo(&ret->moves[3], data+n);
+	ret->item = GET2(data+n);
+	n += 2;
+	memcpy(ret->derived, data+n, 14);
+	n += 14;
+	n += getstatcolo(&ret->ev, data+n);
+	n += getstatcolo(&ret->iv, data+n);
+	ret->friendship = GET2(data+n);
+	n += 2;
+	memcpy(ret->contest, data+n, 11);
+	n += 11;
+	memcpy(ret->ribbon, data+n, 13);
+	n += 13;
+	ret->pkrs = data[n];
+	n += 1;
+	ret->isegg = data[n];
+	n += 1;
+	ret->ability = data[n];
+	n += 1;
+	ret->valid = data[n];
+	n += 1;
+	memcpy(ret->pad2, data+n, 9);
+	n += 9;
+	ret->slot = data[n];
+	n += 1;
+	ret->shadowid = GET2(data+n);
+	n += 2;
+	ret->pad4 = GET2(data+n);
+	n += 2;
+	ret->purification = GET4(data+n);
+	n += 4;
+	memcpy(ret->extra, data+n, 88);
+	n += 88;
+	return n;
+}
+
+long
+putpokemoncolo(uchar *dst, Pokemoncolo *src)
+{
+	long n;
+
+	n = 0;
+	PUT2(dst+n, src->species);
+	n += 2;
+	PUT2(dst+n, src->pad0);
+	n += 2;
+	PUT4(dst+n, src->pid);
+	n += 4;
+	dst[n] = src->version;
+	n += 1;
+	dst[n] = src->curregion;
+	n += 1;
+	dst[n] = src->oriregion;
+	n += 1;
+	dst[n] = src->lang;
+	n += 1;
+	PUT2(dst+n, src->metloc);
+	n += 2;
+	dst[n] = src->metlvl;
+	n += 1;
+	dst[n] = src->ball;
+	n += 1;
+	dst[n] = src->otgender;
+	n += 1;
+	memcpy(dst+n, src->pad3, 3);
+	n += 3;
+	PUT2(dst+n, src->otid);
+	n += 2;
+	PUT2(dst+n, src->otsecretid);
+	n += 2;
+	memcpy(dst+n, src->otname, 22);
+	n += 22;
+	memcpy(dst+n, src->name, 22);
+	n += 22;
+	memcpy(dst+n, src->namecopy, 22);
+	n += 22;
+	PUT2(dst+n, src->pad1);
+	n += 2;
+	PUT4(dst+n, src->exp);
+	n += 4;
+	dst[n] = src->statlvl;
+	n += 1;
+	memcpy(dst+n, src->battle, 23);
+	n += 23;
+	n += putmovecolo(dst+n, &src->moves[0]);
+	n += putmovecolo(dst+n, &src->moves[1]);
+	n += putmovecolo(dst+n, &src->moves[2]);
+	n += putmovecolo(dst+n, &src->moves[3]);
+	PUT2(dst+n, src->item);
+	n += 2;
+	memcpy(dst+n, src->derived, 14);
+	n += 14;
+	n += putstatcolo(dst+n, &src->ev);
+	n += putstatcolo(dst+n, &src->iv);
+	PUT2(dst+n, src->friendship);
+	n += 2;
+	memcpy(dst+n, src->contest, 11);
+	n += 11;
+	memcpy(dst+n, src->ribbon, 13);
+	n += 13;
+	dst[n] = src->pkrs;
+	n += 1;
+	dst[n] = src->isegg;
+	n += 1;
+	dst[n] = src->ability;
+	n += 1;
+	dst[n] = src->valid;
+	n += 1;
+	memcpy(dst+n, src->pad2, 9);
+	n += 9;
+	dst[n] = src->slot;
+	n += 1;
+	PUT2(dst+n, src->shadowid);
+	n += 2;
+	PUT2(dst+n, src->pad4);
+	n += 2;
+	PUT4(dst+n, src->purification);
+	n += 4;
+	memcpy(dst+n, src->extra, 88);
+	n += 88;
+	return n;
+}
+
+long
+gettrainercolo(Trainercolo *ret, uchar *data)
+{
+	long n;
+
+	n = 0;
+	memcpy(ret->name, data+n, 20);
+	n += 20;
+	memcpy(ret->namecopy, data+n, 20);
+	n += 20;
+	ret->pad0 = GET4(data+n);
+	n += 4;
+	ret->id = GET2(data+n);
+	n += 2;
+	ret->secretid = GET2(data+n);
+	n += 2;
+	return n;
+}
+
+long
+puttrainercolo(uchar *dst, Trainercolo *src)
+{
+	long n;
+
+	n = 0;
+	memcpy(dst+n, src->name, 20);
+	n += 20;
+	memcpy(dst+n, src->namecopy, 20);
+	n += 20;
+	PUT4(dst+n, src->pad0);
+	n += 4;
+	PUT2(dst+n, src->id);
+	n += 2;
+	PUT2(dst+n, src->secretid);
+	n += 2;
+	return n;
+}
+
--- /dev/null
+++ b/colodat.h
@@ -1,0 +1,70 @@
+typedef struct Movecolo Movecolo;
+typedef struct Statcolo Statcolo;
+typedef struct Pokemoncolo Pokemoncolo;
+typedef struct Slotcolo Slotcolo;
+typedef struct Trainercolo Trainercolo;
+
+struct Movecolo {
+	u16int id;
+	uchar pp;
+	uchar up;
+};
+
+struct Statcolo {
+	u16int hp;
+	u16int atk;
+	u16int def;
+	u16int spa;
+	u16int spd;
+	u16int spe;
+};
+
+struct Pokemoncolo {
+	u16int species;
+	u16int pad0;
+	u32int pid;
+	uchar version;
+	uchar curregion;
+	uchar oriregion;
+	uchar lang;
+	u16int metloc;
+	uchar metlvl;
+	uchar ball;
+	uchar otgender;
+	uchar pad3[3];
+	u16int otid;
+	u16int otsecretid;
+	uchar otname[22];
+	uchar name[22];
+	uchar namecopy[22];
+	u16int pad1;
+	u32int exp;
+	uchar statlvl;
+	uchar battle[23];
+	Movecolo moves[4];
+	u16int item;
+	uchar derived[14];
+	Statcolo ev;
+	Statcolo iv;
+	u16int friendship;
+	uchar contest[11];
+	uchar ribbon[13];
+	uchar pkrs;
+	uchar isegg;
+	uchar ability;
+	uchar valid;
+	uchar pad2[9];
+	uchar slot;
+	u16int shadowid;
+	u16int pad4;
+	u32int purification;
+	uchar extra[88];
+};
+
+struct Trainercolo {
+	uchar name[20];
+	uchar namecopy[20];
+	u32int pad0;
+	u16int id;
+	u16int secretid;
+};
--- a/dex.c
+++ b/dex.c
@@ -1,5 +1,15 @@
 #include <u.h>
 #include <libc.h>
+#include "colodat.h"
+#include "gen3dat.h"
+#include "pse.h"
+
+char *gnametab[] = {
+	[GRS] "Ruby/Sapphire",
+	[GFRLG] "Fire Red/Leaf Green",
+	[GEM] "Emerald",
+	[GCOLO] "Colosseum",
+};
 
 char *dexfiletab[] = {
 	[0] "bulbasaur",
--- a/fs.c
+++ b/fs.c
@@ -4,7 +4,8 @@
 #include <thread.h>
 #include <9p.h>
 #include "gen3dat.h"
-#include "gen3.h"
+#include "colodat.h"
+#include "pse.h"
 
 Gen3 gen3;
 
--- a/gen3.c
+++ b/gen3.c
@@ -1,12 +1,24 @@
 #include <u.h>
 #include <libc.h>
 #include "gen3dat.h"
-#include "gen3.h"
+#include "colodat.h"
+#include "pse.h"
 
-char *gen3gnametab[] = {
-	[GRS] "Ruby/Sapphire",
-	[GFRLG] "Fire Red/Leaf Green",
-	[GEM] "Emerald",
+enum{
+	STrainer,
+	SInvent,
+	SState,
+	SMisc,
+	SRiv,
+	SPCA,
+	SPCB,
+	SPCC,
+	SPCD,
+	SPCE,
+	SPCF,
+	SPCG,
+	SPCH,
+	SPCI,
 };
 
 int poketab[24][4] = {
@@ -96,12 +108,24 @@
 	long n;
 	uchar buf[32];
 
+	n = f->prec;
 	p = va_arg(f->args, uchar*);
-	n = va_arg(f->args, long);
 	gen3pkstr(buf, p, n);
 	return fmtprint(f, "%s", (char*)buf);
 }
 
+typedef struct Gen3iv Gen3iv;
+struct Gen3iv {
+	uchar hp;
+	uchar atk;
+	uchar def;
+	uchar spe;
+	uchar spatk;
+	uchar spdef;
+	uchar egg;
+	uchar ability;
+};
+
 void
 getgen3iv(Gen3iv *dst, u32int src)
 {
@@ -189,10 +213,77 @@
 }
 
 extern int gen3speciestab[];
+extern char *dexfiletab[];
+extern char *movenametab[];
 
-int
-getgen3dex(u16int species)
+static int
+vdex3(void *v)
 {
+	Pokedat pd;
+	Pokemon *p;
 
-	return gen3speciestab[species]-1;
+	p = v;
+	if(p->otid == 0)
+		return -1;
+	decryptpokemon(&pd, v);
+	return gen3speciestab[pd.g.species]-1;
 }
+
+#pragma varargck type "L" uchar*
+
+static void
+vinit3(void)
+{
+	fmtinstall('L', gen3strfmt);
+}
+
+static int
+vhdr3(char *dst, char *e, void *v, int box)
+{
+	Gen3 *s;
+
+	s = v;
+	dst = seprint(dst, e, "Name: %.*L  ID: %d  Secret ID: %d\n", sizeof s->tr.name, s->tr.name, s->tr.id, s->tr.secretid);
+	dst = seprint(dst, e, "Game: %s  Time Played: %dhr %dmin\n", gnametab[s->type], s->tr.hours, s->tr.min);
+	seprint(dst, e, "Box %d: %.*L\n", box+1, sizeof s->pc.name[box].n, s->pc.name[box].n);
+	return 0;
+}
+
+static int
+vbody3(char *dst, char *e, void *v)
+{
+	Pokemon *p;
+	Pokedat pd;
+	Gen3iv iv;
+
+	p = v;
+	decryptpokemon(&pd, p);
+	dst = seprint(dst, e, "Name: %.*L\n", sizeof p->name, p->name);
+	dst = seprint(dst, e, "OT Name: %.*L  OT ID: %ud  OT Secret ID: %d\n", sizeof p->otname, p->otname, p->otid, p->otsecretid);
+	dst = seprint(dst, e, "National Dex: %d\n", gen3speciestab[pd.g.species]-1);
+	dst = seprint(dst, e, "Shiny: %d\n", gen3shiny(p));
+	dst = seprint(dst, e, "Exp: %d\n", pd.g.exp);
+	dst = seprint(dst, e, "Move 1: %s  Move 2: %s\n", movenametab[pd.a.move1], movenametab[pd.a.move2]);
+	dst = seprint(dst, e, "Move 3: %s  Move 4: %s\n", movenametab[pd.a.move3], movenametab[pd.a.move4]);
+	dst = seprint(dst, e, "[EV] HP: %d  Atk: %d  Def: %d  SpA: %d  SpD: %d  Spe: %d\n", pd.e.hp, pd.e.atk, pd.e.def, pd.e.spatk, pd.e.spdef, pd.e.spd);
+	getgen3iv(&iv, pd.m.iv);
+	seprint(dst, e, "[IV] HP: %d  Atk: %d  Def: %d  SpA: %d  SpD: %d  Spe: %d\n", iv.hp, iv.atk, iv.def, iv.spatk, iv.spdef, iv.spe);
+	return 0;
+}
+
+static void*
+vboxpk3(int box, int i, void *v)
+{
+	Gen3 *s;
+
+	s = v;
+	return s->pc.box + box*30 + i;
+}
+
+View vgen3 = {
+	vinit3,
+	vhdr3,
+	vdex3,
+	vbody3,
+	vboxpk3,
+};
--- a/gen3.h
+++ /dev/null
@@ -1,63 +1,0 @@
-enum{
-	/* Sections */
-	STrainer,
-	SInvent,
-	SState,
-	SMisc,
-	SRiv,
-	SPCA,
-	SPCB,
-	SPCC,
-	SPCD,
-	SPCE,
-	SPCF,
-	SPCG,
-	SPCH,
-	SPCI,
-
-	/* Game Type */
-	GRS,
-	GFRLG,
-	GEM,
-};
-
-extern char* gen3gnametab[];
-
-long getsection(Section*,uchar*);
-long gettrainer(Trainer*,uchar*);
-long getinvent(Invent*,uchar*);
-long getpokedat(Pokedat*,uchar*);
-long getpc(PC*,uchar*);
-
-typedef struct Gen3 Gen3;
-struct Gen3{
-	int type;
-	Section bank1[14];
-	Section bank2[14];
-	Section *active;
-	Trainer tr;
-	Invent inv;
-	PC pc;
-
-	uchar pcbuf[3968*8 + 2000];
-};
-
-typedef struct Gen3iv Gen3iv;
-struct Gen3iv {
-	uchar hp;
-	uchar atk;
-	uchar def;
-	uchar spe;
-	uchar spatk;
-	uchar spdef;
-	uchar egg;
-	uchar ability;
-};
-
-void gen3pkstr(uchar *d, uchar *s, int n);
-int gen3strfmt(Fmt*);
-void getgen3(int fd, Gen3 *save);
-void decryptpokemon(Pokedat *dst, Pokemon *src);
-void getgen3iv(Gen3iv *dst, u32int src);
-int getgen3dex(u16int species);
-int gen3shiny(Pokemon*);
--- a/mkfile
+++ b/mkfile
@@ -6,12 +6,14 @@
 	view\
 
 HFILES=\
-	gen3.h\
+	colodat.h\
 	gen3dat.h\
 
 OFILES=\
+	colo.$O\
 	gen3.$O\
 	gen3dat.$O\
+	colodat.$O\
 	dex.$O\
 
 </sys/src/cmd/mkmany
@@ -18,6 +20,9 @@
 
 gen3dat.c: gen3dat.h
 	dfc -l $prereq > $target
+
+colodat.c: colodat.h
+	dfc -b $prereq > $target
 
 pokesprite:
 	git/clone https://github.com/msikma/pokesprite.git
--- /dev/null
+++ b/pse.h
@@ -1,0 +1,81 @@
+enum {
+	Slotszcolo = 0x1E000,
+};
+
+typedef struct Colo Colo;
+struct Colo {
+	uchar slots[3][Slotszcolo];
+	u32int index[3];
+	uchar gcihdr[0x40];
+	uchar decrypted[Slotszcolo];
+
+	uchar *active;
+	Pokemoncolo pc[30*3];
+	Trainercolo tr;
+	u32int money;
+	u32int coupons;
+};
+
+long getpokemoncolo(Pokemoncolo*,uchar*);
+long gettrainercolo(Trainercolo*,uchar*);
+void getcolo(int fd, Colo *dst);
+
+enum{
+	/* Game Data Type */
+	GNONE,
+	GG3,
+	GCOLO,
+
+	/* Gen3 Cart Type */
+	GRS,
+	GFRLG,
+	GEM,
+};
+
+extern char* gnametab[];
+
+long getsection(Section*,uchar*);
+long gettrainer(Trainer*,uchar*);
+long getinvent(Invent*,uchar*);
+long getpokedat(Pokedat*,uchar*);
+long getpc(PC*,uchar*);
+
+typedef struct Gen3 Gen3;
+struct Gen3 {
+	int type;
+	Section bank1[14];
+	Section bank2[14];
+	Section *active;
+	Trainer tr;
+	Invent inv;
+	PC pc;
+
+	uchar pcbuf[3968*8 + 2000];
+};
+
+void gen3pkstr(uchar *d, uchar *p, int n);
+void getgen3(int fd, Gen3 *save);
+void decryptpokemon(Pokedat *dst, Pokemon *src);
+int gen3shiny(Pokemon*);
+
+typedef struct View View;
+struct View {
+	void (*init)(void);
+	int (*hdr)(char *dst, char *e, void *v, int box);
+	int (*dex)(void *v);
+	int (*body)(char *dst, char *e, void *v);
+	void* (*box)(int box, int i, void *v);
+};
+
+extern View vgen3;
+extern View vcolo;
+
+typedef struct Save Save;
+struct Save {
+	View *view;
+	int type;
+	union {
+		Gen3 gen3;
+		Colo colo;
+	};
+};
--- a/view.c
+++ b/view.c
@@ -7,12 +7,13 @@
 #include <keyboard.h>
 #include <ctype.h>
 #include "gen3dat.h"
-#include "gen3.h"
+#include "colodat.h"
+#include "pse.h"
 
-Gen3 gen3;
+Save save;
 
 int currentbox = 0;
-Pokemon *currentpk = nil;
+void *currentpk = nil;
 Point spwd;
 Image *background, *light;
 
@@ -24,65 +25,74 @@
 static void
 chbox(int x)
 {
+	int max;
+
+	switch(save.type){
+	default:
+	case GG3:
+		max = 13;
+		break;
+	case GCOLO:
+		max = 2;
+		break;
+	}
 	currentbox += x;
 	if(currentbox < 0)
+		currentbox = max;
+	else if(currentbox > max)
 		currentbox = 0;
-	else if(currentbox > 13)
-		currentbox = 13;
 }
 
 static int
-screenprint(Point p, char *format, ...)
+screenprint(Point p, char *s)
 {
-	char buf[256];
-	va_list v;
+	char *y, *dot;
+	Point op;
 
-	va_start(v, format);
-	vsnprint(buf, sizeof buf, format, v);
-	va_end(v);
-	string(screen, p, display->black, ZP, display->defaultfont, buf);
-	return display->defaultfont->height;
+	op = p;
+	for(y = s; (dot = strchr(y, '\n')) != nil; y = dot+1){
+		*dot = 0;
+		string(screen, p, display->black, ZP, display->defaultfont, y);
+		p.y += display->defaultfont->height;
+	}
+	return p.y - op.y;
 }
 
 static void
 redraw(void)
 {
-	char buf[32];
 	char path[128];
+	char buf[512];
 	Image *image;
 	Rectangle r, r2;
-	Point p;
-	Pokedat pd;
-	Gen3iv iv;
 	int i;
 	int fd;
+	int dex;
+	void *p;
 
 	draw(screen, screen->r, background, nil, ZP);
 	r = screen->r;
 	r2 = r;
 	spwd = Pt(68*2, 56*2);
+	save.view->hdr(buf, buf + sizeof buf, &save.gen3, currentbox);
+	r.min.y += screenprint(r.min, buf);
 
-	r.min.y += screenprint(r.min, "Name: %G  ID: %d  Secret ID: %d", gen3.tr.name, sizeof gen3.tr.name, gen3.tr.id, gen3.tr.secretid);
-	r.min.y += screenprint(r.min, "Game: %s  Time Played: %dhr %dmin", gen3gnametab[gen3.type], gen3.tr.hours, gen3.tr.min);
-	r.min.y += screenprint(r.min, "Box %d: %G", currentbox+1, gen3.pc.name[currentbox].n, sizeof gen3.pc.name[currentbox].n);
-
 	if(currentpk == nil)
-		currentpk = gen3.pc.box;
+		currentpk = save.view->box(0, 0, &save.gen3);
 	for(i = 0; i < 30; i++){
 		r2.min.x = r.min.x + (i%6) * spwd.x;
 		r2.min.y = r.min.y + (i/6) * spwd.y;
 		r2.max.x = r2.min.x + spwd.x;
 		r2.max.y = r2.min.y + spwd.y;
-		if(gen3.pc.box + currentbox*30 + i == currentpk)
+		p = save.view->box(currentbox, i, &save.gen3);
+		if(p == currentpk)
 			draw(screen, r2, light, nil, ZP);
-		if(gen3.pc.box[currentbox*30 + i].otid == 0)
+		dex = save.view->dex(p);
+		if(dex > 411 || dex == -1)
 			continue;
-		decryptpokemon(&pd, gen3.pc.box + currentbox*30 + i);
-		if(pd.g.species > 411 || getgen3dex(pd.g.species) == -1)
-			continue;
-		snprint(path, sizeof path, "/sys/games/lib/pokesprite/regular/%s.png", dexfiletab[getgen3dex(pd.g.species)]);
+		snprint(path, sizeof path, "/sys/games/lib/pokesprite/regular/%s.png", dexfiletab[dex]);
 
-		image = spritecache[pd.g.species-1];
+		image = spritecache[dex];
 		if(image == nil){
 			fd = open(path, OREAD);
 			if(fd < 0){
@@ -95,23 +105,13 @@
 				continue;
 		}
 		draw(screen, r2, image, nil, ZP);
-		spritecache[pd.g.species-1] = image;
+		spritecache[dex] = image;
 	}
 
-	decryptpokemon(&pd, currentpk);
 	r = screen->r;
 	r.min.x += 6*spwd.x;
-
-	r.min.y += screenprint(r.min, "Name: %G", currentpk->name, sizeof currentpk->name);
-	r.min.y += screenprint(r.min, "OT Name: %G  OT ID: %ud  OT Secret ID: %d", currentpk->otname, sizeof currentpk->otname, currentpk->otid, currentpk->otsecretid);
-	r.min.y += screenprint(r.min, "National Dex: %d", getgen3dex(pd.g.species));
-	r.min.y += screenprint(r.min, "Shiny: %d", gen3shiny(currentpk));
-	r.min.y += screenprint(r.min, "Exp: %d", pd.g.exp);
-	r.min.y += screenprint(r.min, "Move 1: %s  Move 2: %s", movenametab[pd.a.move1], movenametab[pd.a.move2]);
-	r.min.y += screenprint(r.min, "Move 3: %s  Move 4: %s", movenametab[pd.a.move3], movenametab[pd.a.move4]);
-	r.min.y += screenprint(r.min, "[EV] HP: %d  Atk: %d  Def: %d  SpA: %d  SpD: %d  Spe: %d", pd.e.hp, pd.e.atk, pd.e.def, pd.e.spatk, pd.e.spdef, pd.e.spd);
-	getgen3iv(&iv, pd.m.iv);
-	r.min.y += screenprint(r.min, "[IV] HP: %d  Atk: %d  Def: %d  SpA: %d  SpD: %d  Spe: %d", iv.hp, iv.atk, iv.def, iv.spatk, iv.spdef, iv.spe);
+	save.view->body(buf, buf + sizeof buf, currentpk);
+	r.min.y += screenprint(r.min, buf);
 	flushimage(display, 1);
 }
 
@@ -134,6 +134,7 @@
 	p.y -= screen->r.min.y;
 	p.x -= screen->r.min.x;
 
+	/* FIXME */
 	p.y -= display->defaultfont->height*3;
 
 	p.x /= spwd.x;
@@ -140,7 +141,7 @@
 	p.y /= spwd.y;
 	if(p.x + (p.y*6) > 30)
 		return 0;
-	currentpk = gen3.pc.box +currentbox*30 + (p.x + (p.y*6));
+	currentpk = save.view->box(currentbox, (p.x + (p.y*6)), &save.gen3);
 	return 1;
 }
 
@@ -154,7 +155,7 @@
 void
 usage(void)
 {
-	fprint(2, "usage: %s game.sav\n", argv0);
+	fprint(2, "usage: %s [-3c] file\n", argv0);
 	threadexitsall("usage");
 }
 
@@ -175,11 +176,20 @@
 		{nil, nil, CHANEND},
 	};
 
+	save.type = GNONE;
 	ARGBEGIN{
+	case '3':
+		save.view = &vgen3;
+		save.type = GG3;
+		break;
+	case 'c':
+		save.view = &vcolo;
+		save.type = GCOLO;
+		break;
 	default:
 		usage();
 	}ARGEND;
-	if(argc < 1)
+	if(argc < 1 || save.type == GNONE)
 		usage();
 
 	fd = open(argv[0], OREAD);
@@ -186,8 +196,15 @@
 	if(fd < 0)
 		sysfatal("open: %r");
 
-	fmtinstall('G', gen3strfmt);
-	getgen3(fd, &gen3);
+	save.view->init();
+	switch(save.type){
+	case GG3:
+		getgen3(fd, &save.gen3);
+		break;
+	case GCOLO:
+		getcolo(fd, &save.colo);
+		break;
+	}
 
 	if(initdraw(nil, nil, "pse") < 0)
 		sysfatal("initdraw: %r");