Created
December 11, 2022 22:59
-
-
Save WinterSnowfall/7d87ed156fb65896fdba6e925e54bb2a to your computer and use it in GitHub Desktop.
An adaptation of https://gist.github.com/cxx/6d1d44ce4a6107ed80e0a6c8c5b887c4 for the Atari 50: The Anniversary Celebration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// atari502mame.js - convert some of the arcade game data in Atari 50: The Anniversary Celebration to older MAME ROM sets | |
// | |
// Usage: | |
// node atari502mame.js [ROM directory] | |
// | |
// Requirements: | |
// - Node.js v6 or later | |
// - [Microsoft Windows] .NET Framework 4.5 or later (included in Windows 8/10) | |
// - [Linux] /usr/bin/zip | |
// | |
// Supported (libretro core): | |
// - Asteroids (MAME 2003) | |
// - Asteroids Deluxe (MAME 2003) | |
// - Missile Command (MAME 2003, loads with warnings, but appears playable) | |
// - Tempest (MAME 2003) | |
// | |
// Only partially extracted: | |
// - Black Widow (MAME 2003, unplayable) | |
// - Gravitar (MAME 2003, unplayable) | |
// - Lunar Lander (MAME 2003, unplayable) | |
// - Space Duel (MAME 2003, unplayable) | |
// | |
// Not supported: | |
// - AkkaArrh | |
// - Centipede | |
// - Cloak and Dagger | |
// - Crystal Castles | |
// - Liberator | |
// - Major Havok | |
// - MazeInvaders | |
// - Millipede | |
// - Super Breakout | |
// - Warlords | |
// | |
// Changelog: | |
// - 2022-12-12: Adapt Atari Vault script for Atari 50: The Anniversary Celebration | |
// - 2017-12-20: Place dummy files for missing ROMs. | |
// - 2017-04-24: Initial release. | |
const child_process = require('child_process'); | |
const fs = require('fs'); | |
const os = require('os'); | |
const path = require('path'); | |
function split(buf, size) | |
{ | |
const ret = []; | |
for (let i = 0; i < buf.length; i += size) | |
ret.push(buf.slice(i, Math.min(i+size,buf.length))); | |
return ret; | |
} | |
function split_at(buf, ...pos) | |
{ | |
const ret = []; | |
pos = [0, ...pos, buf.length]; | |
for (let i = 1; i < pos.length; i++) | |
ret.push(buf.slice(pos[i-1], pos[i])); | |
return ret; | |
} | |
function reverse_bmp(buf) | |
{ | |
const off_bits = buf.readUInt32LE(10); | |
const width = buf.readUInt32LE(14+4); | |
const height = buf.readUInt32LE(14+8); | |
const ret = Buffer.allocUnsafe(width * height); | |
for (let i = 0; i < height; i++) { | |
const src_start = off_bits + width * (height-1-i); | |
buf.copy(ret, width*i, src_start, src_start+width); | |
} | |
return ret; | |
} | |
function split_nibbles(buf) | |
{ | |
const upper = Buffer.allocUnsafe(buf.length); | |
const lower = Buffer.allocUnsafe(buf.length); | |
for (let i = 0; i < buf.length; i++) { | |
upper[i] = buf[i] >> 4 & 0xf; | |
lower[i] = buf[i] >> 0 & 0xf; | |
} | |
return [upper, lower]; | |
} | |
function encode_gfx(buf, layout) | |
{ | |
const np = layout.planes; | |
const dest = Buffer.alloc(buf.length * np / 8); | |
if (Array.isArray(layout.total)) { | |
const [num, den] = layout.total; | |
layout = Object.assign({}, layout, { | |
total: dest.length * 8 / layout.charincrement * num / den, | |
planeoffset: layout.planeoffset.map(x => { | |
if (Array.isArray(x)) { | |
let [num, den, add] = x; | |
add = add || 0; | |
return dest.length * 8 * num / den + add; | |
} | |
else | |
return x; | |
}) | |
}); | |
} | |
let i = 0; | |
for (let c = 0; c < layout.total; c++) { | |
const charoffset = layout.charincrement * c; | |
for (let y = 0; y < layout.height; y++) { | |
const yoffset = charoffset + layout.yoffset[y]; | |
for (let x = 0; x < layout.width; x++) { | |
const xoffset = yoffset + layout.xoffset[x]; | |
for (let p = 0; p < np; p++) { | |
const offset = xoffset + layout.planeoffset[p]; | |
dest[offset >> 3] |= | |
((buf[i] >> np-1-p) & 1) << (~offset & 7); | |
} | |
i++; | |
} | |
} | |
} | |
return dest; | |
} | |
function zip(name, dir) | |
{ | |
let cmd; | |
if (os.type() === 'Windows_NT') | |
cmd = `powershell Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::CreateFromDirectory('${dir}', '${name}.zip')`; | |
else | |
cmd = `zip -j ${name}.zip ${dir}/*`; | |
child_process.execSync(cmd); | |
} | |
function convert_roms(name, srcdir, maps) | |
{ | |
const bins = {}; | |
for (const region in maps) { | |
const map = maps[region]; | |
let bin; | |
if (map.input instanceof Buffer) | |
bin = map.input; | |
else { | |
let file; | |
let layout; | |
if (typeof map.input === 'string') | |
file = map.input; | |
else | |
({file, layout} = map.input); | |
bin = fs.readFileSync(path.join(srcdir, file)); | |
if (layout) | |
bin = encode_gfx(bin, layout); | |
} | |
if (map.transform) | |
bin = map.transform(bin); | |
bins[region] = bin; | |
} | |
const dstdir = fs.mkdtempSync(path.join(os.tmpdir(), name)); | |
for (const region in maps) { | |
let bin = bins[region]; | |
let {output} = maps[region]; | |
if (typeof output === 'string') { | |
output = [output]; | |
bin = [bin]; | |
} | |
if (!Array.isArray(bin)) | |
bin = split(bin, bin.length/output.length); | |
for (let i = 0; i < output.length; i++) | |
fs.writeFileSync(path.join(dstdir, output[i]), bin[i]); | |
} | |
zip(name, dstdir); | |
for (const f of fs.readdirSync(dstdir)) | |
fs.unlinkSync(path.join(dstdir, f)); | |
fs.rmdirSync(dstdir); | |
console.log(`saved as ${name}.zip.`); | |
} | |
// from https://github.com/mamedev/mame/blob/b888b8c4edaeccea889b97e1c2df6f914ae6e303/src/mame/drivers/sprint2.cpp | |
// license:BSD-3-Clause | |
// copyright-holders:Mike Balfour | |
const SPRINT2_TILE_LAYOUT = { | |
width: 8, | |
height: 8, | |
total: 64, | |
planes: 1, | |
planeoffset: [0], | |
xoffset: [0, 1, 2, 3, 4, 5, 6, 7], | |
yoffset: [0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38], | |
charincrement: 0x40 | |
}; | |
const SPRINT2_CAR_LAYOUT = { | |
width: 16, | |
height: 8, | |
total: 32, | |
planes: 1, | |
planeoffset: [0], | |
xoffset: [0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, | |
0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8], | |
yoffset: [0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70], | |
charincrement: 0x80 | |
}; | |
function asteroid_mame2003(srcdir) | |
{ | |
convert_roms('asteroid', srcdir, { | |
cpu1: { | |
input: 'Asteroids.rom', | |
output: ['035145.02', '035144.02', '035143.02', '035127.02'] | |
} | |
}); | |
} | |
function astdelux_mame2003(srcdir) | |
{ | |
convert_roms('astdelux', srcdir, { | |
cpu1: { | |
input: 'AsteroidsDeluxe.rom', | |
output: ['036430.02', '036431.02', '036432.02', | |
'036433.03', '036800.02', '036799.01'] | |
} | |
}); | |
} | |
function bwidow_mame2003(srcdir) | |
{ | |
convert_roms('bwidow', srcdir, { | |
cpu1: { // wrong checksums | |
input: 'BlackWidow.rom', | |
output: ['136017.107', '136017.108', '136017.109', '136017.110', | |
'136017.101', '136017.102', '136017.103', '136017.104', | |
'136017.105', '136017.106'], | |
transform: bin => { | |
const a = split_at(bin, 0x0800); | |
return [a[0], ...split(a[1], 0x1000)]; | |
} | |
} | |
}); | |
} | |
function gravitar_mame2003(srcdir) | |
{ | |
convert_roms('gravitar', srcdir, { | |
cpu1: { | |
input: 'Gravitar.rom', | |
output: ['136010.210', '136010.207', '136010.208', '136010.309', | |
'136010.301', '136010.302', '136010.303', '136010.304', | |
'136010.305', '136010.306'], | |
transform: bin => { | |
const a = split_at(bin, 0x0800); | |
return [a[0], ...split(a[1], 0x1000)]; | |
} | |
} | |
}); | |
} | |
function llander_mame2003(srcdir) | |
{ | |
convert_roms('llander', srcdir, { | |
cpu1: { | |
input: 'LunarLander.rom', | |
output: ['034599.01', '034598.01', '034597.01', '034572.02', | |
'034571.02', '034570.01', '034569.02'] | |
} | |
}); | |
} | |
function missile_mame2003(srcdir) | |
{ | |
convert_roms('missile', srcdir, { | |
cpu1: { | |
input: 'MissileCommand.rom', | |
output: ['035820.02', '035821.02', '035822.02', | |
'035823.02', '035824.02', '035825.02'] | |
} | |
}); | |
} | |
function spacduel_mame2003(srcdir) | |
{ | |
convert_roms('spacduel', srcdir, { | |
cpu1: { | |
input: 'SpaceDuel.rom', | |
output: ['136006.106', '136006.107', '136006.201', '136006.102', | |
'136006.103', '136006.104', '136006.105'], | |
transform: bin => { | |
const a = split_at(bin, 0x0800); | |
return [a[0], ...split(a[1], 0x1000)]; | |
} | |
} | |
}); | |
} | |
function tempest3_mame2003(srcdir) | |
{ | |
convert_roms('tempest3', srcdir, { | |
cpu1: { | |
input: 'Tempest.rom', | |
output: ['237.002', '136.002', '235.002', | |
'134.002', '133.002', '138.002'] | |
} | |
}); | |
} | |
const srcdir = process.argv[2] || ''; | |
if (fs.existsSync(path.join(srcdir, 'Asteroids.rom'))) { | |
asteroid_mame2003(srcdir); | |
astdelux_mame2003(srcdir); | |
bwidow_mame2003(srcdir); | |
gravitar_mame2003(srcdir); | |
llander_mame2003(srcdir); | |
missile_mame2003(srcdir); | |
spacduel_mame2003(srcdir); | |
tempest3_mame2003(srcdir); | |
} | |
else | |
console.log('Usage: node atari502mame.js [ROM directory]'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment