networkwm-js 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.MD +3 -2
  2. package/dist/bytemanip.d.ts.map +1 -1
  3. package/dist/bytemanip.js +4 -0
  4. package/dist/cli/decrypt-mp3.d.ts +3 -0
  5. package/dist/cli/decrypt-mp3.d.ts.map +1 -0
  6. package/dist/cli/decrypt-mp3.js +59 -0
  7. package/dist/cli/decrypt-oma.d.ts +2 -0
  8. package/dist/cli/decrypt-oma.d.ts.map +1 -0
  9. package/dist/cli/decrypt-oma.js +25 -0
  10. package/dist/cli/sign-device.d.ts +2 -0
  11. package/dist/cli/sign-device.d.ts.map +1 -0
  12. package/dist/cli/sign-device.js +30 -0
  13. package/dist/cli/table-file-info.d.ts +2 -0
  14. package/dist/cli/table-file-info.d.ts.map +1 -0
  15. package/dist/cli/table-file-info.js +46 -0
  16. package/dist/cli/tagged-oma-info.d.ts +2 -0
  17. package/dist/cli/tagged-oma-info.d.ts.map +1 -0
  18. package/dist/cli/tagged-oma-info.js +192 -0
  19. package/dist/cli.d.ts +3 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +50 -0
  22. package/dist/codecs.d.ts +13 -0
  23. package/dist/codecs.d.ts.map +1 -0
  24. package/dist/codecs.js +40 -0
  25. package/dist/database-abstraction.d.ts +11 -4
  26. package/dist/database-abstraction.d.ts.map +1 -1
  27. package/dist/database-abstraction.js +84 -27
  28. package/dist/databases.d.ts +10 -1
  29. package/dist/databases.d.ts.map +1 -1
  30. package/dist/databases.js +2 -1
  31. package/dist/derive-mp3-key.d.ts +12 -0
  32. package/dist/derive-mp3-key.d.ts.map +1 -0
  33. package/dist/derive-mp3-key.js +109 -0
  34. package/dist/devices.d.ts +9 -2
  35. package/dist/devices.d.ts.map +1 -1
  36. package/dist/devices.js +70 -2
  37. package/dist/encryption.d.ts +2 -2
  38. package/dist/encryption.d.ts.map +1 -1
  39. package/dist/encryption.js +25 -12
  40. package/dist/filesystem/usb-mass-storage-webusb-filesystem.d.ts +2 -0
  41. package/dist/filesystem/usb-mass-storage-webusb-filesystem.d.ts.map +1 -1
  42. package/dist/filesystem/usb-mass-storage-webusb-filesystem.js +8 -1
  43. package/dist/helpers.d.ts +6 -2
  44. package/dist/helpers.d.ts.map +1 -1
  45. package/dist/helpers.js +9 -8
  46. package/dist/id3.d.ts +7 -0
  47. package/dist/id3.d.ts.map +1 -1
  48. package/dist/id3.js +25 -1
  49. package/dist/init-data.d.ts +25 -14
  50. package/dist/init-data.d.ts.map +1 -1
  51. package/dist/init-data.js +23 -15
  52. package/dist/initialization.d.ts +3 -2
  53. package/dist/initialization.d.ts.map +1 -1
  54. package/dist/initialization.js +14 -4
  55. package/dist/mp3.d.ts +9 -0
  56. package/dist/mp3.d.ts.map +1 -0
  57. package/dist/mp3.js +137 -0
  58. package/dist/tables.d.ts +1 -0
  59. package/dist/tables.d.ts.map +1 -1
  60. package/dist/tables.js +3 -0
  61. package/dist/tagged-oma.d.ts +3 -2
  62. package/dist/tagged-oma.d.ts.map +1 -1
  63. package/dist/tagged-oma.js +72 -33
  64. package/dist/test.d.ts +2 -0
  65. package/dist/test.d.ts.map +1 -0
  66. package/dist/test.js +70 -0
  67. package/dist/utils.d.ts +6 -2
  68. package/dist/utils.d.ts.map +1 -1
  69. package/dist/utils.js +35 -12
  70. package/package.json +8 -4
  71. package/dist/functions.d.ts +0 -12
  72. package/dist/functions.d.ts.map +0 -1
  73. package/dist/functions.js +0 -73
package/README.MD CHANGED
@@ -12,11 +12,12 @@ For now, it is impossible to use this library as a standalone application - ther
12
12
  - [x] Track upload (ATRAC3 / ATRAC3+)
13
13
  - [x] DRM reimplementation
14
14
  - [X] Fetching contents' list
15
- - [ ] Track upload (MP3)
15
+ - [X] Track upload (MP3)
16
16
  - [X] Track metadata editing (Title / Album / Artist / Genre)
17
17
  - [X] Track moving
18
18
  - [X] Track deletion
19
- - [ ] OMA file decryption
19
+ - [X] OMA file decryption
20
+ - [X] MP3 device leaf ID derivation
20
21
 
21
22
  ## How to incorporate it into your project?
22
23
 
@@ -1 +1 @@
1
- {"version":3,"file":"bytemanip.d.ts","sourceRoot":"","sources":["../src/bytemanip.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG1E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,MAAU,UAE7D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,MAAU,UAE7D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAG9F;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3F;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAKrD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAOrD"}
1
+ {"version":3,"file":"bytemanip.d.ts","sourceRoot":"","sources":["../src/bytemanip.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG1E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3E;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,MAAU,UAG7D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,GAAE,MAAU,UAG7D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAG9F;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAG3F;AAED,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAKrD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAOrD"}
package/dist/bytemanip.js CHANGED
@@ -22,10 +22,14 @@ function readUint32(data, offset) {
22
22
  exports.readUint32 = readUint32;
23
23
  ;
24
24
  function getUint32(data, offset = 0) {
25
+ if (offset < 0)
26
+ offset += data.length;
25
27
  return (data[offset + 0] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
26
28
  }
27
29
  exports.getUint32 = getUint32;
28
30
  function getUint16(data, offset = 0) {
31
+ if (offset < 0)
32
+ offset += data.length;
29
33
  return (data[offset + 0] << 8) | data[offset + 1];
30
34
  }
31
35
  exports.getUint16 = getUint16;
@@ -0,0 +1,3 @@
1
+ export declare function mainDeriveKey(invocation: string, args: string[]): void;
2
+ export declare function main(invocation: string, args: string[]): void;
3
+ //# sourceMappingURL=decrypt-mp3.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decrypt-mp3.d.ts","sourceRoot":"","sources":["../../src/cli/decrypt-mp3.ts"],"names":[],"mappings":"AAkBA,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAe/D;AAED,wBAAgB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAiBtD"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.main = exports.mainDeriveKey = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const derive_mp3_key_1 = require("../derive-mp3-key");
10
+ const encryption_1 = require("../encryption");
11
+ function getIDFromName(name) {
12
+ const fileName = path_1.default.basename(name).toLowerCase();
13
+ if (fileName.length !== 12 || !fileName.endsWith(".oma") || !fileName.startsWith("10")) {
14
+ throw new Error("Invalid file name!");
15
+ }
16
+ const [fileBase] = fileName.split(".", 2);
17
+ const fileId = parseInt(fileBase, 16);
18
+ if (fileId.toString(16) !== fileBase) {
19
+ throw new Error("Cannot parse file name as ID!");
20
+ }
21
+ return fileId - 0x10000000;
22
+ }
23
+ function mainDeriveKey(invocation, args) {
24
+ if (args.length < 1) {
25
+ console.log(`Usage: ${invocation} <OMA-encapsulated-MP3>`);
26
+ return;
27
+ }
28
+ const id = getIDFromName(args[0]);
29
+ const source = new Uint8Array(fs_1.default.readFileSync(args[0]));
30
+ const PROGRESS_CHARS = ["/", "-", "\\"];
31
+ const trackKey = (0, derive_mp3_key_1.deriveMP3TrackKey)(source, (state, progress, outOf) => {
32
+ if (state !== 'commonness')
33
+ return;
34
+ process.stdout.write(`\rChecking commonness: ${progress + 1}/${outOf} ${PROGRESS_CHARS[progress % PROGRESS_CHARS.length]}`);
35
+ });
36
+ console.log(`\nDerived track key: ${trackKey.toString(16).padStart(8, '0')}`);
37
+ const leafId = (0, encryption_1.getMP3EncryptionKey)(trackKey, id);
38
+ console.log(`Device MP3 key: ${leafId.toString(16).padStart(8, '0')}`);
39
+ }
40
+ exports.mainDeriveKey = mainDeriveKey;
41
+ function main(invocation, args) {
42
+ if (args.length < 3) {
43
+ console.log(`Usage: ${invocation} <device key> <OMA-encapsulated-MP3> <output MP3>`);
44
+ return;
45
+ }
46
+ const source = args[1], dest = args[2];
47
+ const deviceKey = parseInt(args[0], 16);
48
+ const fileId = getIDFromName(args[1]);
49
+ if (!fs_1.default.existsSync(source)) {
50
+ console.log("Source does not exist!");
51
+ return;
52
+ }
53
+ if (fs_1.default.existsSync(dest)) {
54
+ console.log("Destination file exists!");
55
+ return;
56
+ }
57
+ fs_1.default.writeFileSync(dest, (0, derive_mp3_key_1.decryptMP3)(new Uint8Array(fs_1.default.readFileSync(source)), fileId, deviceKey));
58
+ }
59
+ exports.main = main;
@@ -0,0 +1,2 @@
1
+ export declare function main(invocation: string, args: string[]): void;
2
+ //# sourceMappingURL=decrypt-oma.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decrypt-oma.d.ts","sourceRoot":"","sources":["../../src/cli/decrypt-oma.ts"],"names":[],"mappings":"AAGA,wBAAgB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAetD"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.main = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const tagged_oma_1 = require("../tagged-oma");
9
+ function main(invocation, args) {
10
+ if (args.length < 2) {
11
+ console.log(`Usage: ${invocation} <source OMA> <destination OMA>`);
12
+ return;
13
+ }
14
+ const [source, dest] = args;
15
+ if (!fs_1.default.existsSync(source)) {
16
+ console.log("Source does not exist!");
17
+ return;
18
+ }
19
+ if (fs_1.default.existsSync(dest)) {
20
+ console.log("Destination file exists!");
21
+ return;
22
+ }
23
+ fs_1.default.writeFileSync(dest, (0, tagged_oma_1.decryptOMA)(new Uint8Array(fs_1.default.readFileSync(source))));
24
+ }
25
+ exports.main = main;
@@ -0,0 +1,2 @@
1
+ export declare function main(): Promise<void>;
2
+ //# sourceMappingURL=sign-device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-device.d.ts","sourceRoot":"","sources":["../../src/cli/sign-device.ts"],"names":[],"mappings":"AAOA,wBAAsB,IAAI,kBAiBzB"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.main = void 0;
7
+ const encryption_1 = require("../encryption");
8
+ const filesystem_1 = require("../filesystem");
9
+ const helpers_1 = require("../helpers");
10
+ const path_1 = require("path");
11
+ const fs_1 = __importDefault(require("fs"));
12
+ async function main() {
13
+ await (0, encryption_1.initCrypto)();
14
+ (0, encryption_1.importKeys)(new Uint8Array(fs_1.default.readFileSync((0, path_1.join)(__dirname, "..", "..", "EKBROOTS.DES"))));
15
+ const device = await (0, helpers_1.openNewDeviceNode)();
16
+ if (!device) {
17
+ console.log("No device found!");
18
+ return;
19
+ }
20
+ const fs = await (0, helpers_1.createNWJSFS)(device);
21
+ console.log(`Connected to ${device.definition.name}`);
22
+ console.log("Opening session...");
23
+ const session = new filesystem_1.UMSCNWJSSession(fs.driver, fs);
24
+ console.log("Authorizing...");
25
+ await session.performAuthorization();
26
+ console.log("Signing...");
27
+ await session.finalizeSession();
28
+ console.log("Done.");
29
+ }
30
+ exports.main = main;
@@ -0,0 +1,2 @@
1
+ export declare function main(invocation: string, args: string[]): void;
2
+ //# sourceMappingURL=table-file-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table-file-info.d.ts","sourceRoot":"","sources":["../../src/cli/table-file-info.ts"],"names":[],"mappings":"AAUA,wBAAgB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QA+BtD"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.main = void 0;
7
+ const tables_1 = require("../tables");
8
+ const utils_1 = require("../utils");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const logger = new utils_1.Logger();
11
+ const log = logger.log.bind(logger);
12
+ const bumpIndent = logger.bumpIndent.bind(logger);
13
+ const hexDump = utils_1.hexDump.bind(null, log);
14
+ function main(invocation, args) {
15
+ const file = args[0];
16
+ if (!file) {
17
+ console.log(`Usage: ${invocation} <table file>`);
18
+ return;
19
+ }
20
+ const treeFileContents = new Uint8Array(fs_1.default.readFileSync(file));
21
+ const parsedTree = (0, tables_1.parseTable)(treeFileContents);
22
+ log(`Name: ${parsedTree.name}`);
23
+ log(`Classes defined:`);
24
+ bumpIndent(1);
25
+ for (const klass of parsedTree.classes) {
26
+ log(`Class ${klass.className} - starts at 0x${klass.startAddress.toString(16)}, 0x${klass.classLength.toString(16)} bytes long`);
27
+ }
28
+ bumpIndent(-1);
29
+ log(`Classes' contents:`);
30
+ bumpIndent(1);
31
+ for (const content of parsedTree.contents) {
32
+ log(`${content.className}:`);
33
+ bumpIndent(1);
34
+ log(`Starts at: 0x${content.startAddress.toString(16)}`);
35
+ log(`One element is 0x${content.oneElementLength.toString(16)} bytes long`);
36
+ for (let i = 0; i < content.elements.length; i++) {
37
+ log(`Elements[${i}]:`);
38
+ bumpIndent(1);
39
+ hexDump(content.elements[i]);
40
+ bumpIndent(-1);
41
+ }
42
+ bumpIndent(-1);
43
+ }
44
+ bumpIndent(-1);
45
+ }
46
+ exports.main = main;
@@ -0,0 +1,2 @@
1
+ export declare function main(invocation: string, args: string[]): void;
2
+ //# sourceMappingURL=tagged-oma-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tagged-oma-info.d.ts","sourceRoot":"","sources":["../../src/cli/tagged-oma-info.ts"],"names":[],"mappings":"AAiKA,wBAAgB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAoBtD"}
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.main = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const utils_1 = require("../utils");
9
+ const id3_1 = require("../id3");
10
+ const himd_js_1 = require("himd-js");
11
+ const derive_mp3_key_1 = require("../derive-mp3-key");
12
+ let hexDumpAll = false;
13
+ const logger = new utils_1.Logger();
14
+ const log = logger.log.bind(logger);
15
+ const bumpIndent = logger.bumpIndent.bind(logger);
16
+ const hexDump = utils_1.hexDump.bind(null, log);
17
+ const textDecoder = new TextDecoder();
18
+ const utf16Decoder = new TextDecoder("UTF-16BE");
19
+ function parseEncryptionHeader(contents, offset) {
20
+ log("Encryption header (main ID3 header):");
21
+ bumpIndent(1);
22
+ const ea3Header = contents.subarray(offset, offset + 10);
23
+ const headerSizeRemaining = (0, id3_1.readSynchsafeInt32)(new DataView(ea3Header.buffer), 6)[0];
24
+ const fullEncryptionHeader = (0, utils_1.concatUint8Arrays)([ea3Header, contents.subarray(offset + 10, offset + 10 + headerSizeRemaining)]);
25
+ const metadata = (0, id3_1.parse)(fullEncryptionHeader);
26
+ log(`Version: ${metadata.version.major}.${metadata.version.minor}`);
27
+ log(`Flags: ${metadata.flags}`);
28
+ log(`Tags:`);
29
+ bumpIndent(1);
30
+ for (const tag of metadata.tags) {
31
+ if (tag.id === "GEOB") {
32
+ log("Sony GEOB DRM tag");
33
+ bumpIndent(1);
34
+ const HEADER_LENGTHS = {
35
+ OMG_BKLSI: 0x10,
36
+ OMG_ULINF: 0,
37
+ OMG_OLINF: -1,
38
+ };
39
+ const geob = tag.contents;
40
+ if (!(geob[0] === 2 &&
41
+ (0, utils_1.arrayEq)(geob.subarray(1, 1 + 6), textEncoder.encode("binary")) &&
42
+ (0, utils_1.arrayEq)(geob.subarray(1 + 6, 1 + 6 + 3), Array(3).fill(0)))) {
43
+ log("Invalid GLOB contents! Raw data below:");
44
+ hexDump(geob);
45
+ bumpIndent(-1);
46
+ continue;
47
+ }
48
+ let geobName = utf16Decoder.decode(geob.subarray(10, 50));
49
+ const offset = geobName.indexOf("\x00");
50
+ geobName = geobName.substring(0, offset);
51
+ let dataOffset = 10 + offset * 2 + 2;
52
+ log(`Name: ${geobName}`);
53
+ let headerLength = HEADER_LENGTHS[geobName];
54
+ if (headerLength === -1)
55
+ headerLength = geob.length - dataOffset;
56
+ if (headerLength === undefined) {
57
+ log("[!] Warning: Undefined header length for this tag. Assuming 0");
58
+ headerLength = 0;
59
+ }
60
+ const headerData = geob.subarray(dataOffset, dataOffset + headerLength);
61
+ log("Header:");
62
+ bumpIndent(1);
63
+ hexDump(headerData);
64
+ dataOffset += headerLength;
65
+ bumpIndent(-1);
66
+ log("Contents:");
67
+ bumpIndent(1);
68
+ if (!((dataOffset + 16) < geob.length))
69
+ log("<None>");
70
+ while ((dataOffset + 16) < geob.length) {
71
+ let name = textDecoder.decode(geob.subarray(dataOffset, dataOffset + 12));
72
+ let chunkLen = geob[dataOffset + 13];
73
+ let chunkCount = geob[dataOffset + 15];
74
+ if (name.includes("\x00")) {
75
+ name = name.substring(0, name.indexOf("\x00"));
76
+ }
77
+ if (name === "EKB ") {
78
+ log("[!]: Special");
79
+ chunkCount = 1;
80
+ chunkLen = 204;
81
+ }
82
+ const data = geob.subarray(dataOffset + 16, dataOffset + 16 + (chunkCount * chunkLen));
83
+ log(`${name} - Stored in ${chunkCount} ${chunkLen}-byte long chunks:`);
84
+ bumpIndent(1);
85
+ hexDump(data);
86
+ bumpIndent(-1);
87
+ dataOffset += 16 + data.length;
88
+ }
89
+ bumpIndent(-2);
90
+ }
91
+ else {
92
+ if (tag.contents[0] === 2) {
93
+ const rootName = tag.contents.subarray(1);
94
+ let stringValue = utf16Decoder.decode(rootName);
95
+ if (stringValue.includes("\x00")) {
96
+ stringValue = stringValue.substring(0, stringValue.indexOf("\x00"));
97
+ }
98
+ if ((stringValue.length * 2) !== rootName.length) {
99
+ const value = rootName.subarray(stringValue.length * 2 + 2);
100
+ const stringValue2 = utf16Decoder.decode(value);
101
+ log(`${tag.id}<Container>: ${stringValue}: ${stringValue2} <${tag.flags}>`);
102
+ }
103
+ else {
104
+ log(`${tag.id}: ${stringValue} <${tag.flags}>`);
105
+ }
106
+ }
107
+ else
108
+ log(`${tag.id}: <Unknown data> <${tag.flags}>`);
109
+ if (hexDumpAll) {
110
+ bumpIndent(1);
111
+ hexDump(tag.contents);
112
+ bumpIndent(-1);
113
+ }
114
+ }
115
+ }
116
+ bumpIndent(-2);
117
+ return fullEncryptionHeader.length;
118
+ }
119
+ function parseFormatHeader(contents, offset) {
120
+ const length = 96;
121
+ log("EA3 format header:");
122
+ bumpIndent(1);
123
+ const data = contents.subarray(offset, offset + length);
124
+ const prologue = data.subarray(0, 6);
125
+ const PROLOGUE_VALID = new Uint8Array([0x45, 0x41, 0x33, 0x01, 0x00, 0x60]);
126
+ const PROLOGUE_VALID_MP3 = new Uint8Array([0x45, 0x41, 0x33, 0x02, 0x00, 0x60]);
127
+ const epilogue = data.subarray(8, 12);
128
+ const EPILOGUE_VALID = new Uint8Array([0, 0, 0, 0]);
129
+ if (!(0, utils_1.arrayEq)(EPILOGUE_VALID, epilogue) || !((0, utils_1.arrayEq)(PROLOGUE_VALID, prologue) || (0, utils_1.arrayEq)(PROLOGUE_VALID_MP3, prologue))) {
130
+ log("Invalid data found in EA3 format header. Raw data:");
131
+ bumpIndent(1);
132
+ hexDump(data);
133
+ bumpIndent(-2);
134
+ return length;
135
+ }
136
+ const encryptedFlags = (data[6] << 8) | data[7];
137
+ if (encryptedFlags === 0xFFFF) {
138
+ log("File is not encrypted");
139
+ }
140
+ else {
141
+ log(`File is encrypted using method ${encryptedFlags}`);
142
+ }
143
+ ;
144
+ let codecInfo = new Uint8Array([0, 0, 0]);
145
+ let codecId = data[32];
146
+ codecInfo[0] = data[33];
147
+ codecInfo[1] = data[34];
148
+ codecInfo[2] = data[35];
149
+ if (codecId === 3) {
150
+ // MP3
151
+ log(`Codec: MP3`);
152
+ const derivedParams = (0, derive_mp3_key_1.deriveMP3ParametersFromOMA)(new Uint8Array([codecId, ...codecInfo]));
153
+ for (const [k, v] of Object.entries(derivedParams)) {
154
+ log(`${k}: ${v.toString(2)}`);
155
+ }
156
+ bumpIndent(-1);
157
+ return length;
158
+ }
159
+ const codecInfoStruct = { codecId, codecInfo };
160
+ log(`Codec: ${(0, himd_js_1.getCodecName)(codecInfoStruct)}`);
161
+ log(`Bitrate: ${(0, himd_js_1.getKBPS)(codecInfoStruct)}kbps`);
162
+ bumpIndent(-1);
163
+ return length;
164
+ }
165
+ const textEncoder = new TextEncoder();
166
+ const ENCRYPTION_HEADER_START = textEncoder.encode("ea3");
167
+ const FORMAT_HEADER_START = textEncoder.encode("EA3");
168
+ function main(invocation, args) {
169
+ const file = args[0];
170
+ if (!file) {
171
+ console.log(`Usage: ${invocation} <OMA file> [hex-dump-all]`);
172
+ return;
173
+ }
174
+ const contents = new Uint8Array(fs_1.default.readFileSync(file));
175
+ let offset = 0;
176
+ if (args[1] === 'hex-dump-all')
177
+ hexDumpAll = true;
178
+ while (offset < contents.length) {
179
+ const headerStart = contents.subarray(offset, offset + 3);
180
+ if ((0, utils_1.arrayEq)(headerStart, ENCRYPTION_HEADER_START)) {
181
+ offset += parseEncryptionHeader(contents, offset);
182
+ }
183
+ else if ((0, utils_1.arrayEq)(headerStart, FORMAT_HEADER_START)) {
184
+ offset += parseFormatHeader(contents, offset);
185
+ }
186
+ else {
187
+ console.log("<Audio data>");
188
+ break;
189
+ }
190
+ }
191
+ }
192
+ exports.main = main;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const table_file_info_1 = require("./cli/table-file-info");
5
+ const tagged_oma_info_1 = require("./cli/tagged-oma-info");
6
+ const decrypt_oma_1 = require("./cli/decrypt-oma");
7
+ const decrypt_mp3_1 = require("./cli/decrypt-mp3");
8
+ const decrypt_mp3_2 = require("./cli/decrypt-mp3");
9
+ const sign_device_1 = require("./cli/sign-device");
10
+ const path_1 = require("path");
11
+ const commands = [
12
+ {
13
+ name: 'table-info',
14
+ root: table_file_info_1.main,
15
+ },
16
+ {
17
+ name: 'oma-info',
18
+ root: tagged_oma_info_1.main,
19
+ },
20
+ {
21
+ name: 'decrypt-oma',
22
+ root: decrypt_oma_1.main,
23
+ },
24
+ {
25
+ name: 'derive-mp3-key',
26
+ root: decrypt_mp3_1.mainDeriveKey,
27
+ },
28
+ {
29
+ name: 'decrypt-mp3',
30
+ root: decrypt_mp3_2.main,
31
+ },
32
+ {
33
+ name: 'sign-device',
34
+ root: sign_device_1.main,
35
+ }
36
+ ];
37
+ async function main() {
38
+ var _a;
39
+ const subcommand = (_a = process.argv[2]) === null || _a === void 0 ? void 0 : _a.toLowerCase();
40
+ const args = process.argv.slice(3);
41
+ const def = commands.find(e => e.name.toLowerCase() === subcommand);
42
+ if (!def) {
43
+ console.log(`Usage ${(0, path_1.basename)(process.argv[1])} <subcommand> [...arguments], where subcommand is one of:`);
44
+ commands.forEach(e => console.log(`- ${e.name}`));
45
+ return;
46
+ }
47
+ const result = def.root(`${process.argv[1]} ${subcommand}`, args);
48
+ await result;
49
+ }
50
+ main().then(() => process.exit(0));
@@ -0,0 +1,13 @@
1
+ import { HiMDCodec } from "himd-js";
2
+ export declare enum NWCodec {
3
+ MP3 = 3
4
+ }
5
+ export interface NWCodecInfo {
6
+ codecId: HiMDCodec | NWCodec;
7
+ codecInfo: Uint8Array;
8
+ complete?: boolean;
9
+ }
10
+ export declare function createEA3Header({ codecId, codecInfo, complete }: NWCodecInfo, encrypted?: number, version?: number): Uint8Array;
11
+ export declare function getCodecName(codecInfo: NWCodecInfo): import("himd-js").HiMDCodecName;
12
+ export declare function getKBPS(codecInfo: NWCodecInfo): number;
13
+ //# sourceMappingURL=codecs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.d.ts","sourceRoot":"","sources":["../src/codecs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAsD,MAAM,SAAS,CAAC;AAGnG,oBAAY,OAAO;IACf,GAAG,IAAO;CACb;AAED,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7B,SAAS,EAAE,UAAU,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,WAAW,EAAE,SAAS,SAAS,EAAE,OAAO,SAAI,cAO7G;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,WAAW,mCAMlD;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,WAAW,UAa7C"}
package/dist/codecs.js ADDED
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getKBPS = exports.getCodecName = exports.createEA3Header = exports.NWCodec = void 0;
4
+ const himd_js_1 = require("himd-js");
5
+ const bytemanip_1 = require("./bytemanip");
6
+ var NWCodec;
7
+ (function (NWCodec) {
8
+ NWCodec[NWCodec["MP3"] = 3] = "MP3";
9
+ })(NWCodec = exports.NWCodec || (exports.NWCodec = {}));
10
+ function createEA3Header({ codecId, codecInfo, complete }, encrypted = 0xFFFF, version = 1) {
11
+ const headerSize = 96;
12
+ const header = new Uint8Array(headerSize);
13
+ header.set(new Uint8Array([0x45, 0x41, 0x33, version, 0x00, 0x60, ...(0, bytemanip_1.writeUint16)(encrypted), 0x00, 0x00, 0x00, 0x00]));
14
+ header[32] = codecId;
15
+ header.set(complete ? codecInfo : codecInfo.slice(0, 3), 33);
16
+ return header;
17
+ }
18
+ exports.createEA3Header = createEA3Header;
19
+ function getCodecName(codecInfo) {
20
+ if (codecInfo.codecId === NWCodec.MP3) {
21
+ return "MP3";
22
+ }
23
+ return (0, himd_js_1.getCodecName)(codecInfo);
24
+ }
25
+ exports.getCodecName = getCodecName;
26
+ function getKBPS(codecInfo) {
27
+ if (codecInfo.codecId === NWCodec.MP3) {
28
+ // Make it compatible with HiMD codec definitions
29
+ const modifiedCodecInfo = new Uint8Array(5);
30
+ modifiedCodecInfo.fill(0);
31
+ modifiedCodecInfo[0] = 3;
32
+ modifiedCodecInfo.set(codecInfo.codecInfo.subarray(0, 3), 2);
33
+ return (0, himd_js_1.getKBPS)({
34
+ codecInfo: modifiedCodecInfo,
35
+ codecId: himd_js_1.HiMDCodec.ATRAC3PLUS_OR_MPEG,
36
+ });
37
+ }
38
+ return (0, himd_js_1.getKBPS)(codecInfo);
39
+ }
40
+ exports.getKBPS = getKBPS;
@@ -1,6 +1,8 @@
1
1
  import { CodecInfo, HiMDCodecName, HiMDFilesystem } from "himd-js";
2
- import { DatabaseManager, TrackMetadata } from "./databases";
2
+ import { DatabaseManager, InboundTrackMetadata, TrackMetadata } from "./databases";
3
3
  import { UMSCNWJSSession } from "./filesystem";
4
+ import { DeviceDefinition } from "./devices";
5
+ import { NWCodecInfo } from "./codecs";
4
6
  export declare type AbstractedTrack = TrackMetadata & {
5
7
  encryptionState: Uint8Array;
6
8
  codecInfo: Uint8Array;
@@ -11,16 +13,21 @@ export declare type AbstractedTrack = TrackMetadata & {
11
13
  };
12
14
  export declare class DatabaseAbstraction {
13
15
  private filesystem;
16
+ deviceInfo: DeviceDefinition;
14
17
  private content1ArtistAlbumTrack?;
15
18
  private lastTotalDuration;
16
19
  private allTracks;
17
20
  private deletedTracks;
21
+ private mp3DeviceKey?;
18
22
  database: DatabaseManager;
19
23
  private constructor();
20
- static create(filesystem: HiMDFilesystem): Promise<DatabaseAbstraction>;
24
+ static create(filesystem: HiMDFilesystem, deviceInfo: DeviceDefinition): Promise<DatabaseAbstraction>;
21
25
  private _create;
22
- addNewTrack(trackInfo: TrackMetadata, codecInfo: CodecInfo): number;
23
- uploadTrack(trackInfo: TrackMetadata, codec: CodecInfo, rawData: Uint8Array, session?: UMSCNWJSSession, callback?: (done: number, outOf: number) => void): Promise<void>;
26
+ addNewTrack(trackInfo: TrackMetadata, codecInfo: NWCodecInfo, encryptionState: number): number;
27
+ private reassignTrackNumber;
28
+ private copyToFilesystem;
29
+ uploadMP3Track(trackInfo: InboundTrackMetadata, rawData: Uint8Array, callback?: (done: number, outOf: number) => void): Promise<void>;
30
+ uploadTrack(trackInfo: InboundTrackMetadata, codec: CodecInfo, rawData: Uint8Array, session?: UMSCNWJSSession, callback?: (done: number, outOf: number) => void): Promise<void>;
24
31
  flushUpdates(): Promise<void>;
25
32
  deleteTrack(systemIndex: number): Promise<void>;
26
33
  reserializeDatabase(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"database-abstraction.d.ts","sourceRoot":"","sources":["../src/database-abstraction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAyB,MAAM,SAAS,CAAC;AAC1F,OAAO,EAAE,eAAe,EAAc,aAAa,EAAY,MAAM,aAAa,CAAC;AAGnF,OAAO,EAAsB,eAAe,EAAE,MAAM,cAAc,CAAC;AAInE,oBAAY,eAAe,GAAG,aAAa,GAAG;IAC1C,eAAe,EAAE,UAAU,CAAC;IAC5B,SAAS,EAAE,UAAU,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IAEpB,SAAS,EAAE,aAAa,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,qBAAa,mBAAmB;IAMR,OAAO,CAAC,UAAU;IALtC,OAAO,CAAC,wBAAwB,CAAC,CAAyB;IAC1D,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,aAAa,CAAgB;IAC9B,QAAQ,EAAE,eAAe,CAAC;IACjC,OAAO;WAIa,MAAM,CAAC,UAAU,EAAE,cAAc;IAOrD,OAAO,CAAC,OAAO;IAkCR,WAAW,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS;IAyB3D,WAAW,CACb,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,UAAU,EACnB,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI;IAmC9C,YAAY;IAMZ,WAAW,CAAC,WAAW,EAAE,MAAM;IAcrC,mBAAmB;IA6EnB,0BAA0B,IAAK;QAC3B,0BAA0B,EAAE,CAAC,CAAC;QAC9B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE;YACN,0BAA0B,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,eAAe,EAAE,CAAA;SAC9B,EAAE,CAAA;KACN,EAAE;IAIG,QAAQ;IA6BR,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa;CAWjE"}
1
+ {"version":3,"file":"database-abstraction.d.ts","sourceRoot":"","sources":["../src/database-abstraction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,eAAe,EAAc,oBAAoB,EAAE,aAAa,EAAY,MAAM,aAAa,CAAC;AAGzG,OAAO,EAAsB,eAAe,EAAE,MAAM,cAAc,CAAC;AAGnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,WAAW,EAAyB,MAAM,UAAU,CAAC;AAK9D,oBAAY,eAAe,GAAG,aAAa,GAAG;IAC1C,eAAe,EAAE,UAAU,CAAC;IAC5B,SAAS,EAAE,UAAU,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IAEpB,SAAS,EAAE,aAAa,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,qBAAa,mBAAmB;IAOR,OAAO,CAAC,UAAU;IAAyB,UAAU,EAAE,gBAAgB;IAN3F,OAAO,CAAC,wBAAwB,CAAC,CAAyB;IAC1D,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAC,CAAS;IACvB,QAAQ,EAAE,eAAe,CAAC;IACjC,OAAO;WAIa,MAAM,CAAC,UAAU,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB;IA+BnF,OAAO,CAAC,OAAO;IAkCR,WAAW,CAAC,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM;IAyB5F,OAAO,CAAC,mBAAmB;YAWb,gBAAgB;IAsBxB,cAAc,CAChB,SAAS,EAAE,oBAAoB,EAC/B,OAAO,EAAE,UAAU,EACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI;IAe9C,WAAW,CACb,SAAS,EAAE,oBAAoB,EAC/B,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,UAAU,EACnB,OAAO,CAAC,EAAE,eAAe,EACzB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI;IAmB9C,YAAY;IAMZ,WAAW,CAAC,WAAW,EAAE,MAAM;IAcrC,mBAAmB;IA6EnB,0BAA0B,IAAK;QAC3B,0BAA0B,EAAE,CAAC,CAAC;QAC9B,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE;YACN,0BAA0B,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,eAAe,EAAE,CAAA;SAC9B,EAAE,CAAA;KACN,EAAE;IAIG,QAAQ;IA6BR,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa;CAYjE"}