@camoto/gamecode
Advanced tools
Comparing version 0.0.2 to 1.0.0
@@ -1,5 +0,5 @@ | ||
/** | ||
* @file Command line interface to the library. | ||
/* | ||
* Command line interface to the library. | ||
* | ||
* Copyright (C) 2018-2019 Adam Nielsen <malvineous@shikadi.net> | ||
* Copyright (C) 2010-2021 Adam Nielsen <malvineous@shikadi.net> | ||
* | ||
@@ -22,4 +22,7 @@ * This program is free software: you can redistribute it and/or modify | ||
import commandLineArgs from 'command-line-args'; | ||
import GameCode from '../index.js'; | ||
import GameCodeDecompress from '../util/decompress.js'; | ||
import { | ||
all as gamecodeFormats, | ||
decompressEXE, | ||
findHandler as gamecodeFindHandler, | ||
} from '../index.js'; | ||
import Debug from '../util/debug.js'; | ||
@@ -43,5 +46,5 @@ const debug = Debug.extend('cli'); | ||
const content = { | ||
main: GameCodeDecompress(fs.readFileSync(params.target)), | ||
main: decompressEXE(fs.readFileSync(params.target)), | ||
}; | ||
let handlers = GameCode.findHandler(content.main); | ||
let handlers = gamecodeFindHandler(content.main, params.target); | ||
@@ -83,3 +86,9 @@ console.log(handlers.length + ' format handler(s) matched'); | ||
list() { | ||
for (const a of this.code.attributes) { | ||
if (!this.code) { | ||
throw new OperationsError('list: must open a file first.'); | ||
} | ||
const ids = Object.keys(this.code.attributes).sort(); | ||
for (const id of ids) { | ||
const a = this.code.attributes[id]; | ||
console.log(`${a.id}: "${a.value}" ${a.desc ? `// ${a.desc}` : ''}`); | ||
@@ -92,3 +101,3 @@ } | ||
if (params.format) { | ||
handler = GameCode.getHandler(params.format); | ||
handler = gamecodeFormats.find(h => h.metadata().id === params.format); | ||
if (!handler) { | ||
@@ -103,6 +112,6 @@ throw new OperationsError('Invalid format code: ' + params.format); | ||
let content = { | ||
main: GameCodeDecompress(fs.readFileSync(params.target)), | ||
main: decompressEXE(fs.readFileSync(params.target)), | ||
}; | ||
if (!handler) { | ||
let handlers = GameCode.findHandler(content.main); | ||
let handlers = gamecodeFindHandler(content.main, params.target); | ||
if (handlers.length === 0) { | ||
@@ -202,3 +211,3 @@ throw new OperationsError('Unable to identify this executable format.'); | ||
{ | ||
GameCode.listHandlers().forEach(handler => { | ||
for (const handler of gamecodeFormats) { | ||
const md = handler.metadata(); | ||
@@ -209,3 +218,3 @@ console.log(`${md.id}: ${md.title}`); | ||
}); | ||
}); | ||
} | ||
} | ||
@@ -212,0 +221,0 @@ |
@@ -1,5 +0,5 @@ | ||
/** | ||
* @file Executable handler for Cosmo's Cosmic Adventures. | ||
/* | ||
* Executable handler for Cosmo's Cosmic Adventures. | ||
* | ||
* Copyright (C) 2010-2020 Adam Nielsen <malvineous@shikadi.net> | ||
* Copyright (C) 2010-2021 Adam Nielsen <malvineous@shikadi.net> | ||
* | ||
@@ -25,4 +25,3 @@ * This program is free software: you can redistribute it and/or modify | ||
import pkgRecordIOBuffer from '@malvineous/record-io-buffer'; | ||
const { RecordBuffer, RecordType } = pkgRecordIOBuffer; | ||
import { RecordBuffer, RecordType } from '@camoto/record-io-buffer'; | ||
import CodeHandler_Simple from '../interface/CodeHandler_Simple.js'; | ||
@@ -246,3 +245,3 @@ | ||
{ id: 'msg.key.rshift', type: 'stringz', len: 6 }, | ||
{ id: 'msg.key.printscreen', type: 'stringz', len: 6, desc: 'Matches keypad *.' }, | ||
{ id: 'msg.key.pad.star', type: 'stringz', len: 6 }, | ||
{ id: 'msg.key.alt', type: 'stringz', len: 6 }, | ||
@@ -249,0 +248,0 @@ { id: 'msg.key.space', type: 'stringz', len: 6 }, |
135
index.js
@@ -20,87 +20,72 @@ /* | ||
export * from './formats/index.js'; | ||
import * as formats from './formats/index.js'; | ||
export { default as decompressEXE } from './util/decompress.js'; | ||
import Debug from './util/debug.js'; | ||
const debug = Debug.extend('index'); | ||
import exe_cosmo1 from './formats/exe-cosmo1.js'; | ||
const fileTypes = [ | ||
exe_cosmo1, | ||
/** | ||
* Get a list of all the available handlers. | ||
* | ||
* This is preferable to `import *` because most libraries also export utility | ||
* functions like the autodetection routine which would be included even though | ||
* they are not format handlers. | ||
*/ | ||
export const all = [ | ||
...Object.values(formats), | ||
]; | ||
/** | ||
* Main library interface. | ||
* Get a handler by examining the file content. | ||
* | ||
* Ensure the content has been decompressed first if necessary, e.g. by passing | ||
* it through `decompressEXE()` first. | ||
* | ||
* @param {Uint8Array} content | ||
* Executable file content. | ||
* | ||
* @param {string} filename | ||
* Filename where `content` was read from. This is required to identify | ||
* formats where the filename extension is significant. This can be | ||
* omitted for less accurate autodetection. | ||
* | ||
* @return {Array<CodeHandler>} from formats/*.js that can handle the | ||
* format, or an empty array if the format could not be identified. | ||
* | ||
* @example | ||
* import { findHandler as gameCodeFindHandler, decompressEXE } from '@camoto/gamecode'; | ||
* const content = decompressEXE(fs.readFileSync('cosmo1.exe')); | ||
* const handler = gameCodeFindHandler(content, 'cosmo1.exe'); | ||
* if (handler.length === 0) { | ||
* console.log('Unable to identify file format.'); | ||
* } else { | ||
* const md = handler[0].metadata(); | ||
* console.log('File is in ' + md.id + ' format'); | ||
* } | ||
*/ | ||
export default class GameCode | ||
{ | ||
/** | ||
* Get a handler by ID directly. | ||
* | ||
* @param {string} type | ||
* Identifier of desired file format. | ||
* | ||
* @return {CodeHandler} from formats/*.js matching requested code, or null | ||
* if the code is invalid. | ||
* | ||
* @example const handler = GameCode.getHandler('exe-cosmo1'); | ||
*/ | ||
static getHandler(type) | ||
{ | ||
return fileTypes.find(x => type === x.metadata().id); | ||
export function findHandler(content, filename) { | ||
if (content.length === undefined) { | ||
throw new Error('content parameter must be Uint8Array'); | ||
} | ||
/** | ||
* Get a handler by examining the file content. | ||
* | ||
* Ensure the content has been passed through `decompress()` first. | ||
* | ||
* @param {Uint8Array} content | ||
* Executable file content. | ||
* | ||
* @return {Array} of {CodeHandler} from formats/*.js that can handle the | ||
* format, or an empty array if the format could not be identified. | ||
* | ||
* @example | ||
* const content = fs.readFileSync('cosmo1.exe'); | ||
* const handler = GameCode.findHandler(content); | ||
* if (!handler) { | ||
* console.log('Unable to identify file format.'); | ||
* } else { | ||
* const md = handler.metadata(); | ||
* console.log('File is in ' + md.id + ' format'); | ||
* } | ||
*/ | ||
static findHandler(content) | ||
{ | ||
if (content.length === undefined) { | ||
throw new Error('content parameter must be Uint8Array'); | ||
let handlers = []; | ||
for (const x of all) { | ||
const metadata = x.metadata(); | ||
debug(`Trying format handler ${metadata.id} (${metadata.title})`); | ||
const confidence = x.identify(content, filename); | ||
if (confidence.valid === true) { | ||
debug(`Matched ${metadata.id}: ${confidence.reason}`); | ||
handlers = [x]; | ||
break; | ||
} else if (confidence.valid === undefined) { | ||
debug(`Possible match for ${metadata.id}: ${confidence.reason}`); | ||
handlers.push(x); | ||
// keep going to look for a better match | ||
} else { | ||
debug(`Not ${metadata.id}: ${confidence.reason}`); | ||
} | ||
let handlers = []; | ||
fileTypes.some(x => { | ||
const metadata = x.metadata(); | ||
debug(`Trying format handler ${metadata.id} (${metadata.title})`); | ||
const confidence = x.identify(content); | ||
if (confidence.valid === true) { | ||
handlers = [x]; | ||
return true; // exit loop early | ||
} | ||
if (confidence.valid === undefined) { | ||
handlers.push(x); | ||
// keep going to look for a better match | ||
} | ||
debug(` - Handler reported: ${confidence.reason}`); | ||
}); | ||
return handlers; | ||
} | ||
/** | ||
* Get a list of all the available handlers. | ||
* | ||
* This is probably only useful when testing the library. | ||
* | ||
* @return {Array} of file format handlers, with each element being just like | ||
* the return value of getHandler(). | ||
*/ | ||
static listHandlers() { | ||
return fileTypes; | ||
} | ||
return handlers; | ||
}; |
@@ -1,5 +0,5 @@ | ||
/** | ||
* @file Simple list of offsets and property types for executable handlers. | ||
/* | ||
* Simple list of offsets and property types for executable handlers. | ||
* | ||
* Copyright (C) 2010-2020 Adam Nielsen <malvineous@shikadi.net> | ||
* Copyright (C) 2010-2021 Adam Nielsen <malvineous@shikadi.net> | ||
* | ||
@@ -23,4 +23,3 @@ * This program is free software: you can redistribute it and/or modify | ||
import pkgRecordIOBuffer from '@malvineous/record-io-buffer'; | ||
const { RecordBuffer, RecordType } = pkgRecordIOBuffer; | ||
import { RecordBuffer, RecordType } from '@camoto/record-io-buffer'; | ||
import CodeHandler from './CodeHandler.js'; | ||
@@ -105,2 +104,2 @@ | ||
} | ||
}; | ||
} |
@@ -1,5 +0,5 @@ | ||
/** | ||
* @file Base class and defaults for executable format handlers. | ||
/* | ||
* Base class and defaults for executable format handlers. | ||
* | ||
* Copyright (C) 2020-2021 Adam Nielsen <malvineous@shikadi.net> | ||
* Copyright (C) 2010-2021 Adam Nielsen <malvineous@shikadi.net> | ||
* | ||
@@ -90,11 +90,21 @@ * This program is free software: you can redistribute it and/or modify | ||
* | ||
* @return {Boolean} true if the data is definitely in this format, false if | ||
* it is definitely not in this format, and undefined if the data could not | ||
* be positively identified but it's possible it is in this format. | ||
* @param {string} filename | ||
* The executable's filename in case it is relevant, for those formats where | ||
* the filename is significant. | ||
* | ||
* @return {object} with a `.valid` property, set to `true` if the data is | ||
* definitely in this format, `false` if it is definitely not in this | ||
* format, and `undefined` if it's possible the data is in this format but | ||
* there is not enough information to know for certain one way or the other. | ||
* The returned object also has a `.reason` property containing a technical | ||
* although user-friendly explanation as to why the data was decreed to be | ||
* or not be in this format. This is most useful when uncertain or | ||
* rejecting content, as the user can then be informed why. | ||
*/ | ||
// eslint-disable-next-line no-unused-vars | ||
static identify(content) { | ||
static identify(content, filename) { | ||
return { | ||
valid: false, | ||
reason: 'identify() is unimplemented for this format.', | ||
reason: 'The identify() function has not been implemented by the format ' | ||
+ 'handler, so autodetecting this format is not possible.', | ||
}; | ||
@@ -134,2 +144,2 @@ } | ||
} | ||
}; | ||
} |
{ | ||
"name": "@camoto/gamecode", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"description": "Modify executable files used by DOS games", | ||
@@ -31,10 +31,10 @@ "bin": { | ||
"dependencies": { | ||
"@camoto/gamecomp": "^1.1.3", | ||
"@camoto/record-io-buffer": "^2.4.1", | ||
"@camoto/gamecomp": "^2.0.2", | ||
"@camoto/record-io-buffer": "^3.0.0", | ||
"command-line-args": "^5.1.1", | ||
"debug": "^4.3.1" | ||
}, | ||
"devDependencies": { | ||
"command-line-args": "^5.1.1", | ||
"eslint": "^7.10.0", | ||
"mocha": "^8.1.3", | ||
"eslint": "^7.17.0", | ||
"mocha": "^8.2.1", | ||
"mocha-eslint": "^6.0.0" | ||
@@ -41,0 +41,0 @@ }, |
# gamecode.js | ||
Copyright 2010-2020 Adam Nielsen <<malvineous@shikadi.net>> | ||
Copyright 2010-2021 Adam Nielsen <<malvineous@shikadi.net>> | ||
@@ -33,2 +33,4 @@ This is a Javascript library that can modify executable files for a number of | ||
For Arch Linux users the AUR package `gamecodejs` is also available. | ||
### Command line interface | ||
@@ -64,13 +66,15 @@ | ||
import GameCode from '@camoto/gamecode'; | ||
import GameCodeDecompress from '@camoto/gamecode/util/decompress.js'; | ||
import { | ||
all as gamecodeFormats, | ||
decompressEXE, | ||
exe_cosmo1 as formatHandler, | ||
} from '@camoto/gamecode'; | ||
// Read an executable's attributes into memory. | ||
const handler = GameCode.getHandler('exe-cosmo'); | ||
const content = { | ||
// Load the file and UNLZEXE it if needed. | ||
main: GameCodeDecompress(fs.readFileSync('cosmo1.exe')), | ||
main: decompressEXE(fs.readFileSync('cosmo1.exe')), | ||
// Some formats need additional files here, see handler.supps() | ||
}; | ||
let exe = handler.extract(content); | ||
let exe = formatHandler.extract(content); | ||
@@ -84,3 +88,3 @@ // List the attributes. | ||
// Write the .exe back to disk with the modifications. | ||
const outBuffer = handler.patch(content, exe); | ||
const outBuffer = formatHandler.patch(content, exe); | ||
fs.writeFileSync('cosmo1a.exe', outBuffer.main); | ||
@@ -106,24 +110,10 @@ | ||
2. Edit the main `index.js` and add a `require()` statement for your new file. | ||
3. Make a folder in `test/` for your new format and populate it with | ||
files similar to the other formats. The tests work by creating | ||
a standard archive file with some preset files in it, and | ||
comparing the result to what is inside this folder. | ||
You can either create these archives by hand, with another utility, or if | ||
you are confident that your code is correct, from the code itself. This is | ||
done by setting an environment variable when running the tests, which will | ||
cause the archive file produced by your code to be saved to a temporary | ||
file in the current directory: | ||
SAVE_FAILED_TEST=1 npm test | ||
mv error1.bin test/exe-myformat/default.bin | ||
2. Edit `formats/index.js` and add a line for your new file. | ||
If your archive format has any sort of compression or encryption, | ||
these algorithms should go into the `gamecomp` project instead. This | ||
is to make it easier to reuse the algorithms, as many of them | ||
(particularly the compression ones) are used amongst many unrelated | ||
archive formats. All the `gamecomp` algorithms are available to be | ||
used by any archive format in this library. | ||
If your file format has any sort of compression or encryption, these algorithms | ||
should go into the [gamecompjs](https://github.com/Malvineous/gamecompjs) | ||
project instead. This is to make it easier to reuse the algorithms, as many of | ||
them (particularly the compression ones) are used amongst many unrelated file | ||
formats. All the gamecompjs algorithms are available to be used by any format | ||
in this library. | ||
@@ -133,6 +123,6 @@ During development you can test your code like this: | ||
# Read a sample song and list its details, with debug messages on | ||
$ DEBUG='gamecode:*' ./bin/gamemus open -f mus-myformat example.dat list | ||
$ DEBUG='gamecode:*' ./bin/gamecode open -f exe-myformat example.exe list | ||
# Make sure the format is identified correctly or if not why not | ||
$ DEBUG='gamecode:*' ./bin/gamemus identify example.dat | ||
$ DEBUG='gamecode:*' ./bin/gamecode identify example.exe | ||
@@ -139,0 +129,0 @@ If you use `debug()` rather than `console.log()` in your code then these |
@@ -20,3 +20,3 @@ /* | ||
import cmp_lzexe from '@malvineous/gamecomp/compress/cmp-lzexe.js'; | ||
import cmp_lzexe from '@camoto/gamecomp/compress/cmp-lzexe.js'; | ||
@@ -23,0 +23,0 @@ /** |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
41906
3
13
966
0
4
127
+ Addedcommand-line-args@^5.1.1
+ Added@camoto/gamecomp@2.7.0(transitive)
+ Added@camoto/record-io-buffer@3.3.0(transitive)
+ Addedarray-back@3.1.0(transitive)
+ Addedcommand-line-args@5.2.1(transitive)
+ Addedfind-replace@3.0.0(transitive)
+ Addedlodash.camelcase@4.3.0(transitive)
+ Addedtypical@4.0.0(transitive)
- Removed@camoto/gamecomp@1.1.3(transitive)
- Removed@camoto/record-io-buffer@2.4.1(transitive)
Updated@camoto/gamecomp@^2.0.2