Comparing version 2.0.0 to 2.0.1
#!/usr/bin/env node | ||
import{Command as t}from"commander";import{resolve as e}from"path";import o from"fs-extra";import r from"chalk-template";import n from"crc-32";async function a(t){try{return o.readFile(t,null)}catch{const o=e(t);throw new Error(`Could not read file ${t} (${o}), it may not exist or you do not have permissions to read it.`)}}async function s(t,r){try{await o.outputFile(t,r,null)}catch{const o=e(t);throw new Error(`Could not write to file ${t} (${o}), make sure you have the relevant permissions.`)}}function c(t){const e=t instanceof Error?t.message:t;console.log(r`{bold.red ✖} ${e}`)}function i(t){console.log(r`{bold.green ✔} ${t}`)}const f=Object.freeze({LE:827543618,BE:1112560433}),u=Object.freeze({SourceRead:0,TargetRead:1,SourceCopy:2,TargetCopy:3});function h(t){return{type:u.SourceRead,length:t}}function l(t){return{type:u.TargetRead,bytes:t,length:t.length}}function g(t,e){return{type:u.SourceCopy,offset:t,length:e}}function p(t,e){return{type:u.TargetCopy,offset:t,length:e}}function d(t){return n.buf(t)>>>0}function y(t,e){let o=0,r=1,n=0,a=e;for(;;){if(n=t.getUint8(a++),o+=(127&n)*r,128&n)return[o,a];r<<=7,o+=r}}function w(t){const e=new DataView(t.buffer);let o=!0,r=0,n=0,a=0,s=0,c=[],i=0,w=0,b=0;const m=e.getUint32(r,!0);if(m===f.BE)o=!1;else if(m!==f.LE)throw new Error("Patch is not valid, it does not start with a valid `BPS1` header.");for(r+=4,[n,r]=y(e,r),[a,r]=y(e,r),[s,r]=y(e,r),r+=s;r<e.byteLength-12;){let t=0,n=0,a=0;switch([t,r]=y(e,r),n=1+(t>>2),3&t){case u.SourceRead:a=h(n);break;case u.TargetRead:{let t=new Array(n);for(let a=0;a<n;++a)t[a]=e.getUint8(r++,o);a=l(t)}break;case u.SourceCopy:{let t=0;[t,r]=y(e,r),t=(1&t?-1:1)*(t>>1),a=g(t,n)}break;case u.TargetCopy:{let t=0;[t,r]=y(e,r),t=(1&t?-1:1)*(t>>1),a=p(t,n)}}c.push(a)}if(i=e.getUint32(r+0,o),w=e.getUint32(r+4,o),b=e.getUint32(r+8,o),d(t.subarray(0,-4))!==b)throw new Error(`Patch is invalid, it does not have the expected checksum ${b}.`);return{instructions:{sourceSize:n,sourceChecksum:i,targetSize:a,targetChecksum:w,actions:c},checksum:b}}function b(t,e){let o=t.getUint8(e);return e<t.byteLength-1&&(o|=t.getUint8(e+1)<<8),o}class m{#t=new Array(65536);constructor(t=null){if(t)for(let e=0,o=t.byteLength;e<o;++e)this.addWordLocation(b(t,e),e)}addWordLocation(t,e){const o=this.#t[t];o?o.push(e):this.#t[t]=[e]}getWordLocations(t){return this.#t[t]??[]}}function U(t,e,{outputOffset:o}){let r=0,n=o;for(;n<t.byteLength&&n<e.byteLength&&t.getUint8(n)===e.getUint8(n);)r++,n++;return h(r)}function L(t,{outputOffset:e,targetReadLength:o}){const r=[],n=e-o;for(let e=0;e<o;++e)r.push(t.getUint8(n+e));return l(r)}function k(t,e,{word:o,outputOffset:r,sourceRelativeOffset:n,sourceWordTable:a}){let s=0,c=0;const i=a.getWordLocations(o);for(const o of i){let n=0,a=o,i=r;for(;a<t.byteLength&&i<e.byteLength&&t.getUint8(a++)===e.getUint8(i++);)n++;n>s&&(s=n,c=o)}return g(c-n,s)}function O(t,{word:e,outputOffset:o,targetRelativeOffset:r,targetWordTable:n}){let a=0,s=0;const c=n.getWordLocations(e);for(const e of c){let r=0,n=e,c=o;for(;c<t.byteLength&&t.getUint8(n++)===t.getUint8(c++);)r++;r>a&&(a=r,s=e)}return p(s-r,a)}function v(...t){let e=t[0];for(let o=1,r=t.length;o<r;++o)t[o].length>e.length&&(e=t[o]);return e}function R(t){let e=0,o=t;for(;;){if(e++,o>>=7,0===o)return e;o--}}function C(t,e,o){let r=o,n=e;for(;;){let e=127&r;if(r>>=7,0===r){t.setUint8(n++,128|e);break}t.setUint8(n++,e),r--}return n}function S(t,e,o){let r=C(t,e,(o.length-1<<2)+o.type);return o.type===u.TargetRead?o.bytes.forEach((e=>t.setUint8(r++,e))):o.type!==u.SourceCopy&&o.type!==u.TargetCopy||(r=C(t,r,(Math.abs(o.offset)<<1)+(o.offset<0?1:0))),r}function T({sourceSize:t,sourceChecksum:e,targetSize:o,targetChecksum:r,actions:n}){const a=4+R(t)+R(o)+R(0)+n.reduce(((t,e)=>t+function(t){let e=R((t.length-1<<2)+t.type);return t.type===u.TargetRead?e+=t.length:t.type!==u.SourceCopy&&t.type!==u.TargetCopy||(e+=R((Math.abs(t.offset)<<1)+(t.offset<0?1:0))),e}(e)),0)+4+4+4,s=new DataView(new ArrayBuffer(a)),c=new Uint8Array(s.buffer);let i=0;s.setUint32(i,f.LE,!0),i=4,i=C(s,i,t),i=C(s,i,o),i=C(s,i,0);for(const t of n)i=S(s,i,t);s.setUint32(i,e,!0),s.setUint32(i+4,r,!0);const h=d(c.subarray(0,-4));return s.setUint32(i+8,h,!0),{buffer:c,checksum:h}}const E=(new t).name("bps").version("1.0.0").description("A tool for creating and applying BPS patches.");E.command("verify").description("verifies a patch file").argument("<patch>","the patch file to read").action((async function(t){try{const{checksum:e}=w(await a(t));i(`Patch has checksum ${e} and is valid.`)}catch(t){c(t)}})),E.command("apply").description("applies a patch to a file").argument("<patch>","the patch file to apply").argument("<source>","the source file to patch").argument("<output>","the location to write the patched file to").action((async function(t,e,o){try{const{instructions:r}=w(await a(t)),n=function({sourceChecksum:t,targetSize:e,targetChecksum:o,actions:r},n){if(d(n)!==t)throw new Error("Source is not compatible with the patch, it does not have the expected checksum.");const a=new DataView(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)),s=new DataView(new ArrayBuffer(e));let c=0,i=0,f=0;for(const t of r)switch(t.type){case u.SourceRead:for(let e=0;e<t.length;++e)s.setUint8(c+e,a.getUint8(c+e));c+=t.length;break;case u.TargetRead:for(let e=0,o=t.bytes.length;e<o;++e)s.setUint8(c++,t.bytes[e]);break;case u.SourceCopy:i+=t.offset;for(let e=0;e<t.length;++e)s.setUint8(c++,a.getUint8(i++));break;case u.TargetCopy:f+=t.offset;for(let e=0;e<t.length;++e)s.setUint8(c++,s.getUint8(f++));break;default:throw new Error("Patch is invalid, it contains an invalid action type.")}const h=new Uint8Array(s.buffer);if(d(h)!==o)throw new Error("Resulting target is not valid, it does not have the expected checksum.");return h}(r,await a(e));await s(o,n),i("Patch was applied successfully.")}catch(t){c(t)}})),E.command("create").description("creates a patch from a source and a desired target.").argument("<source>","the source file").argument("<target>","the target file").argument("<output>","the location to write the patch to").action((async function(t,e,o){try{const r=T(function(t,e){const o=t.byteLength,r=d(t),n=e.byteLength,a=d(e),s=[],c=new DataView(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)),i=new DataView(e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)),f={outputOffset:0,word:0,sourceRelativeOffset:0,sourceWordTable:new m(c),targetRelativeOffset:0,targetWordTable:new m,targetReadLength:0};for(;f.outputOffset<n;){f.word=b(i,f.outputOffset);const t=U(c,i,f),e=k(c,i,f),o=O(i,f);f.targetWordTable.addWordLocation(f.word,f.outputOffset);const r=v(t,e,o);if(r.length<4){const t=Math.min(1,n-f.outputOffset);f.targetReadLength+=t,f.outputOffset+=t}else f.targetReadLength&&(s.push(L(i,f)),f.targetReadLength=0),f.outputOffset+=r.length,r.type===u.SourceCopy&&(f.sourceRelativeOffset=r.offset+f.sourceRelativeOffset+r.length),r.type===u.TargetCopy&&(f.targetRelativeOffset=r.offset+f.targetRelativeOffset+r.length),s.push(r)}return f.targetReadLength&&s.push(L(i,f)),{sourceSize:o,sourceChecksum:r,targetSize:n,targetChecksum:a,actions:s}}(await a(t),await a(e)));await s(o,r.buffer),i(`Patch was generated successfully with checksum ${r.checksum}.`)}catch(t){c(t)}})),E.parse(process.argv); | ||
import{Command as t}from"commander";import{resolve as e}from"path";import o from"fs-extra";import r from"chalk-template";import n from"crc-32";async function a(t){try{return o.readFile(t,null)}catch{const o=e(t);throw new Error(`Could not read file ${t} (${o}), it may not exist or you do not have permissions to read it.`)}}async function s(t,r){try{await o.outputFile(t,r,null)}catch{const o=e(t);throw new Error(`Could not write to file ${t} (${o}), make sure you have the relevant permissions.`)}}function c(t){const e=t instanceof Error?t.message:t;console.log(r`{bold.red ✖} ${e}`)}function i(t){console.log(r`{bold.green ✔} ${t}`)}const f=Object.freeze({LE:827543618,BE:1112560433}),u=Object.freeze({SourceRead:0,TargetRead:1,SourceCopy:2,TargetCopy:3});function h(t){return{type:u.SourceRead,length:t}}function l(t){return{type:u.TargetRead,bytes:t,length:t.length}}function g(t,e){return{type:u.SourceCopy,offset:t,length:e}}function p(t,e){return{type:u.TargetCopy,offset:t,length:e}}function d(t){return n.buf(t)>>>0}function y(t,e){let o=0,r=1,n=0,a=e;for(;;){if(n=t.getUint8(a++),o+=(127&n)*r,128&n)return[o,a];r<<=7,o+=r}}function w(t){const e=new DataView(t.buffer);let o=!0,r=0,n=0,a=0,s=0,c=[],i=0,w=0,b=0;const m=e.getUint32(r,!0);if(m===f.BE)o=!1;else if(m!==f.LE)throw new Error("Patch is not valid, it does not start with a valid `BPS1` header.");for(r+=4,[n,r]=y(e,r),[a,r]=y(e,r),[s,r]=y(e,r),r+=s;r<e.byteLength-12;){let t=0,n=0,a=0;switch([t,r]=y(e,r),n=1+(t>>2),3&t){case u.SourceRead:a=h(n);break;case u.TargetRead:{let t=new Array(n);for(let a=0;a<n;++a)t[a]=e.getUint8(r++,o);a=l(t)}break;case u.SourceCopy:{let t=0;[t,r]=y(e,r),t=(1&t?-1:1)*(t>>1),a=g(t,n)}break;case u.TargetCopy:{let t=0;[t,r]=y(e,r),t=(1&t?-1:1)*(t>>1),a=p(t,n)}}c.push(a)}if(i=e.getUint32(r+0,o),w=e.getUint32(r+4,o),b=e.getUint32(r+8,o),d(t.subarray(0,-4))!==b)throw new Error(`Patch is invalid, it does not have the expected checksum ${b}.`);return{instructions:{sourceSize:n,sourceChecksum:i,targetSize:a,targetChecksum:w,actions:c},checksum:b}}function b(t,e){let o=t.getUint8(e);return e<t.byteLength-1&&(o|=t.getUint8(e+1)<<8),o}class m{#t=new Array(65536);constructor(t=null){if(t)for(let e=0,o=t.byteLength;e<o;++e)this.addWordLocation(b(t,e),e)}addWordLocation(t,e){const o=this.#t[t];o?o.push(e):this.#t[t]=[e]}getWordLocations(t){return this.#t[t]??[]}}function U(t,e,{outputOffset:o}){let r=0,n=o;for(;n<t.byteLength&&n<e.byteLength&&t.getUint8(n)===e.getUint8(n);)r++,n++;return h(r)}function L(t,{outputOffset:e,targetReadLength:o}){const r=[],n=e-o;for(let e=0;e<o;++e)r.push(t.getUint8(n+e));return l(r)}function k(t,e,{word:o,outputOffset:r,sourceRelativeOffset:n,sourceWordTable:a}){let s=0,c=0;const i=a.getWordLocations(o);for(const o of i){let n=0,a=o,i=r;for(;a<t.byteLength&&i<e.byteLength&&t.getUint8(a++)===e.getUint8(i++);)n++;n>s&&(s=n,c=o)}return g(c-n,s)}function O(t,{word:e,outputOffset:o,targetRelativeOffset:r,targetWordTable:n}){let a=0,s=0;const c=n.getWordLocations(e);for(const e of c){let r=0,n=e,c=o;for(;c<t.byteLength&&t.getUint8(n++)===t.getUint8(c++);)r++;r>a&&(a=r,s=e)}return p(s-r,a)}function v(...t){let e=t[0];for(let o=1,r=t.length;o<r;++o)t[o].length>e.length&&(e=t[o]);return e}function R(t){let e=0,o=t;for(;;){if(e++,o>>=7,0===o)return e;o--}}function C(t,e,o){let r=o,n=e;for(;;){let e=127&r;if(r>>=7,0===r){t.setUint8(n++,128|e);break}t.setUint8(n++,e),r--}return n}function S(t,e,o){let r=C(t,e,(o.length-1<<2)+o.type);return o.type===u.TargetRead?o.bytes.forEach((e=>t.setUint8(r++,e))):o.type!==u.SourceCopy&&o.type!==u.TargetCopy||(r=C(t,r,(Math.abs(o.offset)<<1)+(o.offset<0?1:0))),r}function T({sourceSize:t,sourceChecksum:e,targetSize:o,targetChecksum:r,actions:n}){const a=4+R(t)+R(o)+R(0)+n.reduce(((t,e)=>t+function(t){let e=R((t.length-1<<2)+t.type);return t.type===u.TargetRead?e+=t.length:t.type!==u.SourceCopy&&t.type!==u.TargetCopy||(e+=R((Math.abs(t.offset)<<1)+(t.offset<0?1:0))),e}(e)),0)+4+4+4,s=new DataView(new ArrayBuffer(a)),c=new Uint8Array(s.buffer);let i=0;s.setUint32(i,f.LE,!0),i=4,i=C(s,i,t),i=C(s,i,o),i=C(s,i,0);for(const t of n)i=S(s,i,t);s.setUint32(i,e,!0),s.setUint32(i+4,r,!0);const h=d(c.subarray(0,-4));return s.setUint32(i+8,h,!0),{buffer:c,checksum:h}}const E=(new t).name("bps").version("2.0.1").description("A tool for creating and applying BPS patches.");E.command("verify").description("verifies a patch file").argument("<patch>","the patch file to read").action((async function(t){try{const{checksum:e}=w(await a(t));i(`Patch has checksum ${e} and is valid.`)}catch(t){c(t)}})),E.command("apply").description("applies a patch to a file").argument("<patch>","the patch file to apply").argument("<source>","the source file to patch").argument("<output>","the location to write the patched file to").action((async function(t,e,o){try{const{instructions:r}=w(await a(t)),n=function({sourceChecksum:t,targetSize:e,targetChecksum:o,actions:r},n){if(d(n)!==t)throw new Error("Source is not compatible with the patch, it does not have the expected checksum.");const a=new DataView(n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength)),s=new DataView(new ArrayBuffer(e));let c=0,i=0,f=0;for(const t of r)switch(t.type){case u.SourceRead:for(let e=0;e<t.length;++e)s.setUint8(c+e,a.getUint8(c+e));c+=t.length;break;case u.TargetRead:for(let e=0,o=t.bytes.length;e<o;++e)s.setUint8(c++,t.bytes[e]);break;case u.SourceCopy:i+=t.offset;for(let e=0;e<t.length;++e)s.setUint8(c++,a.getUint8(i++));break;case u.TargetCopy:f+=t.offset;for(let e=0;e<t.length;++e)s.setUint8(c++,s.getUint8(f++));break;default:throw new Error("Patch is invalid, it contains an invalid action type.")}const h=new Uint8Array(s.buffer);if(d(h)!==o)throw new Error("Resulting target is not valid, it does not have the expected checksum.");return h}(r,await a(e));await s(o,n),i("Patch was applied successfully.")}catch(t){c(t)}})),E.command("create").description("creates a patch from a source and a desired target.").argument("<source>","the source file").argument("<target>","the target file").argument("<output>","the location to write the patch to").action((async function(t,e,o){try{const r=T(function(t,e){const o=t.byteLength,r=d(t),n=e.byteLength,a=d(e),s=[],c=new DataView(t.buffer.slice(t.byteOffset,t.byteOffset+t.byteLength)),i=new DataView(e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)),f={outputOffset:0,word:0,sourceRelativeOffset:0,sourceWordTable:new m(c),targetRelativeOffset:0,targetWordTable:new m,targetReadLength:0};for(;f.outputOffset<n;){f.word=b(i,f.outputOffset);const t=U(c,i,f),e=k(c,i,f),o=O(i,f);f.targetWordTable.addWordLocation(f.word,f.outputOffset);const r=v(t,e,o);if(r.length<4){const t=Math.min(1,n-f.outputOffset);f.targetReadLength+=t,f.outputOffset+=t}else f.targetReadLength&&(s.push(L(i,f)),f.targetReadLength=0),f.outputOffset+=r.length,r.type===u.SourceCopy&&(f.sourceRelativeOffset=r.offset+f.sourceRelativeOffset+r.length),r.type===u.TargetCopy&&(f.targetRelativeOffset=r.offset+f.targetRelativeOffset+r.length),s.push(r)}return f.targetReadLength&&s.push(L(i,f)),{sourceSize:o,sourceChecksum:r,targetSize:n,targetChecksum:a,actions:s}}(await a(t),await a(e)));await s(o,r.buffer),i(`Patch was generated successfully with checksum ${r.checksum}.`)}catch(t){c(t)}})),E.parse(process.argv); |
@@ -5,2 +5,8 @@ # Changelog | ||
## 2.0.1 - 2024-02-25 | ||
### Fixed | ||
- The CLI tool now reports the correct version. | ||
## 2.0.0 - 2024-02-24 | ||
@@ -7,0 +13,0 @@ |
{ | ||
"name" : "bps", | ||
"version" : "2.0.0", | ||
"version" : "2.0.1", | ||
@@ -6,0 +6,0 @@ "type" : "module", |
33383