Skip to content

Instantly share code, notes, and snippets.

@Fordi
Created November 17, 2016 00:30
Show Gist options
  • Save Fordi/464bf4e61b346bc5be20b07a85807878 to your computer and use it in GitHub Desktop.
Save Fordi/464bf4e61b346bc5be20b07a85807878 to your computer and use it in GitHub Desktop.
Game Genie code conversion library (ported from uggconv.c)
var ConsoleCode = (function () {
var fmt = function (n, b, p) {
if (n === null || n === undefined) { return ''; }
if (isNaN(n)) { return ''; }
n = n.toString(b).toUpperCase();
while (p > n.length) {
n = '0' + n;
}
return n;
};
function GameBoy(address, value, check, description) {
if (!(this instanceof GameBoy)) {
throw new Error("GameBoy is a constructor, not a function");
}
if (arguments.length >= 3 && !isNaN(value) && !isNaN(address)) {
this.address = address;
this.value = value;
if (isNaN(check)) {
this.description = check;
} else {
this.check = check;
this.description = description;
}
return;
}
this.description = value;
address = String(address);
var isGG = address.match(/^([a-f0-9]{3})-([a-f0-9]{2}[a-f9])(?:-([a-f0-9]{3}))?$/i);
var isRaw = address.match(/^([a-f0-9]{4}):([a-f0-9]{2})(?::([a-f0-9]{2}))?$/i);
if (isRaw) {
this.address = parseInt(isRaw[1], 16);
this.value = parseInt(isRaw[2], 16);
if (isRaw[2]) {
this.check = parseInt(isRaw[3], 16);
}
return;
}
if (isGG) {
this.value = parseInt(address[0] + address[1], 16) & 0xff;
this.address = parseInt(address[2] + address[4] + address[5], 16) | ((~parseInt(address[6], 16) & 0xf) << 12);
if (isGG[3]) {
this.check = (~parseInt(address[8] + address[10], 16)) & 0xff;
this.check = (((this.check & 0xfc) >> 2) | ((this.check & 0x03) << 6)) ^ 0x45;
}
return;
}
throw new Error([
"The constructor for " + this.name + " accepts the following signatures:",
this.name + "(String gameGenieCode ('XXX-XXX' or 'XXX-XXX-XXX'), [String description])",
this.name + "(String rawCode ('AAAA:VV' or 'AAAA:VV:CC', [String description])",
this.name + "(Number address, Number value, [Number check], [String description])"
].join("\n - "));
}
GameBoy.prototype = {
name: "GameBoy",
constructor: GameBoy,
toGameGenie: function () {
var addr = fmt(this.address, 16, 4);
var value = fmt(this.value, 16, 2);
var out = [
value[0] + value[1] + addr[1],
addr[2] + addr[3] + (~parseInt(addr[0]) & 0xf).toString(16)
];
if (this.check !== undefined) {
var ch = this.check ^ 0x45;
ch = ~(((ch & 0xc0) >> 6) | ((ch & 0x3f) << 2));
var i = (ch & 0xf0) >> 4;
out.push((i & 0xf).toString(16) + ((i ^ 8) & 0xf).toString(16) + (ch & 0xf).toString(16));
}
return out.join('-').toUpperCase();
},
toHigan: function () {
return this.toRaw().replace(/:/g, '/');
},
toRaw: function () {
var out = [
fmt(this.address, 16, 4),
fmt(this.value, 16, 2)
];
if (this.check != undefined) {
out.push(fmt(this.check, 16, 2));
}
return out.join(':');
}
};
function Megadrive(address, value, description) {
if (!(this instanceof Megadrive)) {
throw new Error("Megadrive is a constructor, not a function");
}
if (arguments.length >= 2 && !isNaN(value) && !isNaN(address)) {
this.address = address;
this.value = value;
this.description = description;
return;
}
address = String(address);
this.description = value;
var isGG = address.match(/^([A-Z0-9]{4})-([A-Z0-9]{4})$/i);
var isRaw = address.match(/^([A-F0-9]{6}):([A-F0-9]{4})$/i);
if (isRaw) {
this.address = parseInt(isRaw[1]);
this.value = parseInt(isRaw[2]);
return;
}
if (isGG) {
var data = (isGG[1] + isGG[2]).split('').map(function (v) { return this.constructor.chars.decode[v]; }, this);
this.address = ((data[3] & 0x0f) << 20) |
((data[4] & 0x1e) << 15) |
((data[1] & 0x03) << 14) |
((data[2] & 0x1f) << 9) |
((data[3] & 0x10) << 4) |
((data[6] & 0x07) << 5) |
(data[7] & 0x1f);
this.value = ((data[5] & 0x01) << 15) |
((data[6] & 0x18) << 10) |
((data[4] & 0x01) << 12) |
((data[5] & 0x1e) << 7) |
((data[0] & 0x1f) << 3) |
((data[1] & 0x16) >> 2);
return;
}
throw new Error([
"The constructor for " + this.name + " accepts the following signatures:",
this.name + "(String gameGenieCode ('XXXX-XXXX'), [String description])",
this.name + "(String rawCode ('AAAAAA:VVVV'', [String description])",
this.name + "(Number address, Number value, [String description])"
].join("\n - "));
}
Megadrive.prototype = {
name: "Megadrive",
constructor: Megadrive,
toRaw: function () {
return [ fmt(this.address, 16, 6), fmt(this.value, 16, 4)].join(':');
},
toHigan: function () {
return this.toRaw().replace(/:/g, '/');
},
toGameGenie: function () {
var data = [ 0, 0, 0, 0, 0, 0, 0, 0 ];
data[3] |= (this.address >> 20) & 0x0f;
data[4] |= (this.address >> 15) & 0x1e;
data[1] |= (this.address >> 14) & 0x03;
data[2] |= (this.address >> 9) & 0x1f;
data[3] |= (this.address >> 4) & 0x10;
data[6] |= (this.address >> 5) & 0x07;
data[7] |= (this.address & 0x1f);
data[5] |= (this.value >> 15) & 0x01;
data[6] |= (this.value >> 10) & 0x18;
data[4] |= (this.value >> 12) & 0x01;
data[5] |= (this.value >> 7) & 0x1e;
data[0] |= (this.value >> 3) & 0x1f;
data[1] |= (this.value << 2) & 0x16;
data = data.map(function (i) { return this.constructor.chars.encode[i]; }, this);
return [ data.slice(0, 4).join(''), data.slice(4).join('') ].join('-');
}
}
Megadrive.chars = {};
Megadrive.chars.encode = 'ABCDEFGHJKLMNPRSTVWXYZ0123456789'.split('');
Megadrive.chars.decode = Megadrive.chars.encode.reduce(function (o, ch, idx) {
o[ch] = idx;
o[ch.toLowerCase()] = idx;
return o;
}, {});
function NES(address, value, check, description) {
if (!(this instanceof NES)) {
throw new Error("NES is a constructor, not a function");
}
if (arguments.length >= 3 && !isNaN(value) && !isNaN(address)) {
this.address = address;
this.value = value;
if (isNaN(check)) {
this.description = check;
} else {
this.check = check;
this.description = description;
}
return;
}
address = String(address);
this.description = value;
var isGG = /^[APZLGITYEOXUKSVN]{6,8}/.test(address);
var isRaw = address.match(/^([a-f0-9]{4}):([a-f0-9]{2})(?::([a-f0-9]{2}))?$/i);
if (isRaw) {
this.address = parseInt(isRaw[1], 16);
this.value = parseInt(isRaw[2], 16);
if (isRaw[2]) {
this.check = parseInt(isRaw[3], 16);
}
return;
} else if (isGG) {
var data = address.split('').map(function (v) { return this.constructor.chars.decode[v]; }, this);
this.address = 0x8000 |
((data[1] & 8) << 4) |
((data[2] & 7) << 4) |
((data[3] & 7) << 12) |
((data[3] & 8) << 0) |
((data[4] & 7) << 0) |
((data[4] & 8) << 8) |
((data[5] & 7) << 8);
if (address.length === 8) {
this.value = ((data[0] & 7) << 0) |
((data[0] & 8) << 4) |
((data[1] & 7) << 4) |
((data[7] & 8) << 0);
this.check = ((data[6] & 7) << 0) |
((data[6] & 8) << 0) |
((data[6] & 8) << 4) |
((data[7] & 7) << 4);
} else {
this.value = ((data[0] & 7) << 0) |
((data[0] & 8) << 4) |
((data[1] & 7) << 4) |
((data[5] & 8) << 0);
}
return;
}
throw new Error([
"The constructor for " + this.name + " accepts the following signatures:",
this.name + "(String gameGenieCode ('XXXXXX' or 'XXXXXXXX'), [String description])",
this.name + "(String rawCode ('AAAA:VV' or 'AAAA:VV:CC', [String description])",
this.name + "(Number address, Number value, [Number check], [String description])"
].join("\n - "));
}
NES.prototype = {
name: "NES",
constructor: NES,
toRaw: function () {
var out = [
fmt(this.address, 16, 4),
fmt(this.value, 16, 2)
];
if (this.check != undefined) {
out.push(fmt(this.check, 16, 2));
}
return out.join(':');
},
toHigan: function () {
return this.toRaw().replace(/:/g, '/');
},
toGameGenie: function () {
var data = isNaN(this.check) ? [0, 0, 0, 0, 0, 0] : [0, 0, 0, 0, 0, 0, 0, 0];
var address = this.address & 0x7fff;
//encodeNES(v, n, m, s) data[n] |= (v >> s) & m
data[0] = ((this.value >> 0) & 7) |
((this.value >> 4) & 8);
data[1] = ((this.address >> 4) & 8) |
((this.value >> 4) & 7);
data[2] = (this.address >> 4) & 7;
data[3] = ((address >> 12) & 7) |
((address >> 0) & 8);
data[4] = ((address >> 0) & 7) |
((address >> 8) & 8);
data[5] = ((this.address >> 8) & 7) |
// Differs from original uggconv:
// placing this outside the `no-check` enables truncated with-check code to operate unchecked by dropping last two chars
((this.value >> 0) & 8);
if (!isNaN(this.check)) {
data[6] = ((this.check >> 0) & 7) |
((this.check >> 0) & 8) |
((this.check >> 4) & 8);
data[7] = ((this.check >> 4) & 7) |
((this.value >> 0) & 8);
}
return data.map(function (v) { return this.constructor.chars.encode[v]; }, this).join('');
}
};
NES.chars = {};
NES.chars.encode = 'APZLGITYEOXUKSVN'.split('');
NES.chars.decode = NES.chars.encode.reduce(function (o, ch, idx) {
o[ch] = idx;
o[ch.toLowerCase()] = idx;
return o;
}, {});
function SuperNES(address, value, description) {
if (!(this instanceof SuperNES)) {
throw new Error("SuperNES is a constructor, not a function");
}
if (arguments.length >= 2) {
this.address = address;
this.value = value;
this.description = description;
return;
}
address = String(address);
this.description = value;
var isRaw = address.match(/^([0-9a-z]{6})\:([0-9a-z]{2})$/i);
var isGG = address.match(/^([0-9a-z]{4})-([0-9a-z]{4})$/i);
if (isRaw) {
this.address = parseInt(isRaw[1], 16);
this.value = parseInt(isRaw[2], 16);
return;
}
if (isGG) {
var data = (isGG[1] + isGG[2]).split('').map(function (v) {
return this.constructor.chars.decode[v];
}, this);
this.value = data[0] << 4 + data[1];
var cp = (data[2] << 20) + (data[3] << 16) + (data[4] << 12) + (data[5] << 8) + (data[6] << 4) + data[7];
console.log(cp);
this.address = (((cp & (0xc00000 >> 0)) << 0) >> 8) |
(((cp & (0xc00000 >> 2)) << 2) >> 10) |
(((cp & (0xc00000 >> 4)) << 4) >> 16) |
(((cp & (0xc00000 >> 6)) << 6) >> 18) |
(((cp & (0xc00000 >> 8)) << 8) >> 14) |
(((cp & (0xc00000 >> 10)) << 10) >> 0) |
(((cp & (0xc00000 >> 12)) << 12) >> 2) |
(((cp & (0xc00000 >> 14)) << 14) >> 20) |
(((cp & (0xc00000 >> 16)) << 16) >> 22) |
(((cp & (0xc00000 >> 18)) << 18) >> 4) |
(((cp & (0xc00000 >> 20)) << 20) >> 6) |
(((cp & (0xc00000 >> 22)) << 22) >> 12);
return;
}
throw new Error([
"The constructor for " + this.name + " accepts the following signatures:",
this.name + "(String gameGenieCode ('XXXX-XXXX'), [String description])",
this.name + "(String rawCode ('AAAAAA:VV', [String description])",
this.name + "(Number address, Number value, [String description])"
].join("\n - "));
}
SuperNES.prototype = {
name: "SuperNES",
constructor: SuperNES,
toRaw: function () {
return [ fmt(this.address, 16, 6), fmt(this.value, 16, 2)].join(':');
},
toHigan: function () {
return this.toRaw().replace(/:/g, '/');
},
toGameGenie: function () {
var cp = (((this.address & (0xc00000 >> 8)) << 8) >> 0) |
(((this.address & (0xc00000 >> 10)) << 10) >> 2) |
(((this.address & (0xc00000 >> 16)) << 16) >> 4) |
(((this.address & (0xc00000 >> 18)) << 18) >> 6) |
(((this.address & (0xc00000 >> 14)) << 14) >> 8) |
(((this.address & (0xc00000 >> 0)) << 0) >> 10) |
(((this.address & (0xc00000 >> 2)) << 2) >> 12) |
(((this.address & (0xc00000 >> 20)) << 20) >> 14) |
(((this.address & (0xc00000 >> 22)) << 22) >> 16) |
(((this.address & (0xc00000 >> 4)) << 4) >> 18) |
(((this.address & (0xc00000 >> 6)) << 6) >> 20) |
(((this.address & (0xc00000 >> 12)) << 12) >> 22);
return [
[
this.constructor.chars.encode[this.value >> 4],
this.constructor.chars.encode[this.value & 0xf],
this.constructor.chars.encode[((cp & 0xf00000) >> 20) & 0xf],
this.constructor.chars.encode[((cp & 0x0f0000) >> 16) & 0xf]
].join(''),
[
this.constructor.chars.encode[((cp & 0x00f000) >> 12) & 0xf],
this.constructor.chars.encode[((cp & 0x000f00) >> 8) & 0xf],
this.constructor.chars.encode[((cp & 0x0000f0) >> 4) & 0xf],
this.constructor.chars.encode[((cp & 0x00000f) ) & 0xf]
].join('')
].join('-');
}
}
SuperNES.chars = {};
SuperNES.chars.encode = 'DF4709156BC8A23E'.split('');
SuperNES.chars.decode = SuperNES.chars.encode.reduce(function (o, ch, idx) {
o[ch] = idx;
o[ch.toLowerCase()] = idx;
return o;
}, {});
return {
GameBoy: GameBoy,
GameGear: GameBoy,
NES: NES,
SuperNES: SuperNES,
Megadrive: Megadrive
}
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment