diff --git a/.changeset/tender-crews-perform.md b/.changeset/tender-crews-perform.md new file mode 100644 index 00000000..4ee6f4f1 --- /dev/null +++ b/.changeset/tender-crews-perform.md @@ -0,0 +1,5 @@ +--- +"@nx.js/ncm": patch +--- + +Add `@nx.js/ncm` package diff --git a/docs/app/toggle.tsx b/docs/app/toggle.tsx index 40b79a30..e656e29e 100644 --- a/docs/app/toggle.tsx +++ b/docs/app/toggle.tsx @@ -1,5 +1,6 @@ import { TbClockBolt, + TbDatabaseEdit, TbGlobe, TbBrowser, TbBraces, @@ -42,6 +43,12 @@ export function Toggle() { url: '/inspect', icon: , }, + { + title: '@nx.js/ncm', + description: 'Content manager API', + url: '/ncm', + icon: , + }, { title: '@nx.js/repl', description: 'Read-Eval-Print Loop for nx.js', diff --git a/docs/content/ncm/index.mdx b/docs/content/ncm/index.mdx new file mode 100644 index 00000000..2f9c277a --- /dev/null +++ b/docs/content/ncm/index.mdx @@ -0,0 +1,43 @@ +--- +title: Usage +description: "Content Manager (ncm) service IPC wrapper for nx.js applications" +--- + +The `@nx.js/ncm` package provides a wrapper for the `ncm` service IPC, which +allows for management of the Nintendo Content storages and meta databases. + +In short, use this package for installing titles to your Switch. + +## Installation + + + + +```bash +npm install @nx.js/ncm +``` + + + + +```bash +pnpm add @nx.js/ncm +``` + + + + +```bash +yarn add @nx.js/ncm +``` + + + + +```bash +bun add @nx.js/ncm +``` + + + + diff --git a/docs/content/ncm/meta.json b/docs/content/ncm/meta.json new file mode 100644 index 00000000..0db30f5b --- /dev/null +++ b/docs/content/ncm/meta.json @@ -0,0 +1,10 @@ +{ + "root": true, + "pages": [ + "--- Documentation ---", + "index", + "...", + "--- API Reference ---", + "...api" + ] +} diff --git a/docs/package.json b/docs/package.json index 89f969f7..ed8fd67c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -22,6 +22,7 @@ "@nx.js/constants": "workspace:*", "@nx.js/clkrst": "workspace:*", "@nx.js/http": "workspace:*", + "@nx.js/ncm": "workspace:*", "@nx.js/repl": "workspace:*", "@types/mdx": "^2.0.13", "@types/node": "20", diff --git a/packages/ncm/package.json b/packages/ncm/package.json new file mode 100644 index 00000000..d90316dd --- /dev/null +++ b/packages/ncm/package.json @@ -0,0 +1,40 @@ +{ + "name": "@nx.js/ncm", + "version": "0.0.0", + "description": "Content Manager (ncm) service IPC wrapper for nx.js", + "repository": { + "type": "git", + "url": "https://github.com/TooTallNate/nx.js.git", + "directory": "packages/ncm" + }, + "homepage": "https://nxjs.n8.io/ncm", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "test": "vitest", + "docs": "typedoc && ../../type-aliases-meta.sh" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@nx.js/constants": "^0.3.0" + }, + "devDependencies": { + "@nx.js/runtime": "workspace:*", + "vite": "^5.4.2", + "vitest": "^2.0.5" + }, + "keywords": [ + "nx.js", + "switch", + "ncm", + "content", + "manager" + ], + "author": "Nathan Rajlich ", + "license": "MIT" +} diff --git a/packages/ncm/src/content-meta-database.ts b/packages/ncm/src/content-meta-database.ts new file mode 100644 index 00000000..ee8d6594 --- /dev/null +++ b/packages/ncm/src/content-meta-database.ts @@ -0,0 +1,141 @@ +import { SfBufferAttr } from '@nx.js/constants'; +import { ncm } from './service'; +import type { + NcmContentId, + NcmContentMetaKey, + NcmContentType, + NcmStorageId, +} from './types'; + +export class NcmContentMetaDatabase { + static open(storageId: NcmStorageId) { + //Result ncmOpenContentMetaDatabase(NcmContentMetaDatabase* out_content_meta_database, NcmStorageId storage_id) { + // return serviceDispatchIn(&g_ncmSrv, 5, storage_id, + // .out_num_objects = 1, + // .out_objects = out_content_meta_database, + // ); + //} + const out = new Switch.Service(); + const inArr = new Uint8Array([storageId]); + ncm.dispatchIn(5, inArr.buffer, { + outObjects: [out], + }); + return new NcmContentMetaDatabase(out); + } + + #srv: Switch.Service; + + /** + * @private + */ + constructor(srv: Switch.Service) { + this.#srv = srv; + } + + set(key: NcmContentMetaKey, data: ArrayBuffer) { + //Result ncmContentMetaDatabaseSet(NcmContentMetaDatabase* db, const NcmContentMetaKey* key, const void* data, u64 data_size) { + // return serviceDispatchIn(&db->s, 0, *key, + // .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + // .buffers = { { data, data_size } }, + // ); + //} + this.#srv.dispatchIn(0, key, { + bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.In], + buffers: [data], + }); + } + + get(key: NcmContentMetaKey, data: ArrayBuffer): bigint { + //Result ncmContentMetaDatabaseGet(NcmContentMetaDatabase* db, const NcmContentMetaKey* key, u64* out_size, void* out_data, u64 out_data_size) { + // return serviceDispatchInOut(&db->s, 1, *key, *out_size, + // .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + // .buffers = { { out_data, out_data_size } }, + // ); + //} + const out = new BigUint64Array(1); + this.#srv.dispatchInOut(1, key, out.buffer, { + bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.Out], + buffers: [data], + }); + return out[0]; + } + + delete(key: NcmContentMetaKey) { + //Result ncmContentMetaDatabaseRemove(NcmContentMetaDatabase* db, const NcmContentMetaKey *key) { + // return serviceDispatchIn(&db->s, 2, *key); + //} + this.#srv.dispatchIn(2, key); + } + + getContentIdByType( + key: NcmContentMetaKey, + type: NcmContentType, + ): NcmContentId { + //Result ncmContentMetaDatabaseGetContentIdByType(NcmContentMetaDatabase* db, NcmContentId* out_content_id, const NcmContentMetaKey* key, NcmContentType type) { + // const struct { + // u8 type; + // u8 padding[7]; + // NcmContentMetaKey key; + // } in = { type, {0}, *key }; + // return serviceDispatchInOut(&db->s, 3, in, *out_content_id); + //} + const inData = new ArrayBuffer(0x18); + const inArr = new Uint8Array(inData); + inArr[0] = type; + inArr.set(new Uint8Array(key), 0x8); + const out = new ArrayBuffer(0x10); + this.#srv.dispatchInOut(3, inData, out); + return out as NcmContentId; + } + + has(key: NcmContentMetaKey): boolean { + //Result ncmContentMetaDatabaseHas(NcmContentMetaDatabase* db, bool* out, const NcmContentMetaKey* key) { + // u8 tmp=0; + // Result rc = serviceDispatchInOut(&db->s, 8, *key, tmp); + // if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + // return rc; + //} + const out = new Uint8Array(1); + this.#srv.dispatchInOut(8, key, out.buffer); + return Boolean(out[0] & 1); + } + + hasAll(keys: NcmContentMetaKey[]): boolean { + //Result ncmContentMetaDatabaseHasAll(NcmContentMetaDatabase* db, bool* out, const NcmContentMetaKey* keys, s32 count) { + // u8 tmp=0; + // Result rc = serviceDispatchOut(&db->s, 9, *out, + // .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + // .buffers = { { keys, count*sizeof(NcmContentMetaKey) } }, + // ); + // if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + // return rc; + //} + const inData = new ArrayBuffer(keys.length * 0x10); + const inArr = new Uint8Array(inData); + for (let i = 0; i < keys.length; i++) { + inArr.set(new Uint8Array(keys[i]), i * 0x10); + } + const out = new Uint8Array(1); + this.#srv.dispatchOut(9, out.buffer, { + bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.In], + buffers: [inData], + }); + return Boolean(out[0] & 1); + } + + getSize(key: NcmContentMetaKey): bigint { + //Result ncmContentMetaDatabaseGetSize(NcmContentMetaDatabase* db, u64* out_size, const NcmContentMetaKey* key) { + // return serviceDispatchInOut(&db->s, 10, *key, *out_size); + //} + const out = new BigUint64Array(1); + this.#srv.dispatchInOut(10, key, out.buffer); + return out[0]; + } + + commit() { + //Result ncmContentMetaDatabaseCommit(NcmContentMetaDatabase* db) { + // return _ncmCmdNoIO(&db->s, 15); + //} + this.#srv.dispatch(15); + } +} diff --git a/packages/ncm/src/content-storage.ts b/packages/ncm/src/content-storage.ts new file mode 100644 index 00000000..1076c293 --- /dev/null +++ b/packages/ncm/src/content-storage.ts @@ -0,0 +1,326 @@ +import { SfBufferAttr } from '@nx.js/constants'; +import { ncm } from './service'; +import type { NcmContentId, NcmPlaceHolderId, NcmStorageId } from './types'; + +export class NcmContentStorage { + static open(storageId: NcmStorageId) { + //Result ncmOpenContentStorage(NcmContentStorage* out_content_storage, NcmStorageId storage_id) { + // return serviceDispatchIn(&g_ncmSrv, 4, storage_id, + // .out_num_objects = 1, + // .out_objects = out_content_storage, + // ); + //} + const out = new Switch.Service(); + const inArr = new Uint8Array([storageId]); + ncm.dispatchIn(4, inArr.buffer, { + outObjects: [out], + }); + return new NcmContentStorage(out); + } + + #srv: Switch.Service; + + /** + * @private + */ + constructor(srv: Switch.Service) { + this.#srv = srv; + } + + generatePlaceHolderId(): NcmPlaceHolderId { + //Result ncmContentStorageGeneratePlaceHolderId(NcmContentStorage* cs, NcmPlaceHolderId* out_id) { + // return serviceDispatchOut(&cs->s, 0, *out_id); + //} + const uuid = new ArrayBuffer(0x10); + this.#srv.dispatchOut(0, uuid); + return uuid as NcmPlaceHolderId; + } + + createPlaceHolder( + contentId: NcmContentId, + placeholderId: NcmPlaceHolderId, + size: bigint, + ) { + //Result ncmContentStorageCreatePlaceHolder(NcmContentStorage* cs, const NcmContentId* content_id, const NcmPlaceHolderId* placeholder_id, s64 size) { + // if (hosversionBefore(16,0,0)) { + // const struct { + // NcmContentId content_id; + // NcmPlaceHolderId placeholder_id; + // s64 size; + // } in = { *content_id, *placeholder_id, size }; + // return serviceDispatchIn(&cs->s, 1, in); + // } else { + // const struct { + // NcmPlaceHolderId placeholder_id; + // NcmContentId content_id; + // s64 size; + // } in = { *placeholder_id, *content_id, size }; + // return serviceDispatchIn(&cs->s, 1, in); + // } + //} + const inData = new ArrayBuffer(0x28); + const inDataView = new Uint8Array(inData); + inDataView.set(new Uint8Array(contentId), 0); + inDataView.set(new Uint8Array(placeholderId), 0x10); + new BigInt64Array(inData, 0x20, 1)[0] = size; + this.#srv.dispatchIn(1, inData); + } + + deletePlaceHolder(placeholderId: NcmPlaceHolderId) { + //Result ncmContentStorageDeletePlaceHolder(NcmContentStorage* cs, const NcmPlaceHolderId* placeholder_id) { + // return _ncmCmdInPlaceHolderId(&cs->s, placeholder_id, 2); + //} + this.#srv.dispatchIn(2, placeholderId); + } + + hasPlaceHolder(placeholderId: NcmPlaceHolderId): boolean { + //Result ncmContentStorageHasPlaceHolder(NcmContentStorage* cs, bool* out, const NcmPlaceHolderId* placeholder_id) { + // u8 tmp=0; + // Result rc = serviceDispatchInOut(&cs->s, 3, *placeholder_id, tmp); + // if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + // return rc; + //} + const out = new Uint8Array(1); + this.#srv.dispatchInOut(3, placeholderId, out.buffer); + return Boolean(out[0] & 1); + } + + writePlaceHolder( + placeholderId: NcmPlaceHolderId, + offset: bigint, + data: ArrayBuffer, + ) { + //Result ncmContentStorageWritePlaceHolder(NcmContentStorage* cs, const NcmPlaceHolderId* placeholder_id, u64 offset, const void* data, size_t data_size) { + // const struct { + // NcmPlaceHolderId placeholder_id; + // u64 offset; + // } in = { *placeholder_id, offset }; + // return serviceDispatchIn(&cs->s, 4, in, + // .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + // .buffers = { { data, data_size } }, + // ); + //} + const inData = new ArrayBuffer(0x18); + const inDataView = new Uint8Array(inData); + inDataView.set(new Uint8Array(placeholderId), 0); + new BigUint64Array(inData, 0x10, 1)[0] = offset; + this.#srv.dispatchIn(4, inData, { + bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.In], + buffers: [data], + }); + } + + register(contentId: NcmContentId, placeholderId: NcmPlaceHolderId) { + //Result ncmContentStorageRegister(NcmContentStorage* cs, const NcmContentId* content_id, const NcmPlaceHolderId* placeholder_id) { + // if (hosversionBefore(16,0,0)) { + // const struct { + // NcmContentId content_id; + // NcmPlaceHolderId placeholder_id; + // } in = { *content_id, *placeholder_id }; + // return serviceDispatchIn(&cs->s, 5, in); + // } else { + // const struct { + // NcmPlaceHolderId placeholder_id; + // NcmContentId content_id; + // } in = { *placeholder_id, *content_id }; + // return serviceDispatchIn(&cs->s, 5, in); + // } + //} + const inData = new ArrayBuffer(0x20); + const inDataView = new Uint8Array(inData); + inDataView.set(new Uint8Array(placeholderId), 0); + inDataView.set(new Uint8Array(contentId), 0x10); + this.#srv.dispatchIn(5, inData); + } + + delete(contentId: NcmContentId) { + //Result ncmContentStorageDelete(NcmContentStorage* cs, const NcmContentId* content_id) { + // return _ncmCmdInContentId(&cs->s, content_id, 6); + //} + this.#srv.dispatchIn(6, contentId); + } + + has(contentId: NcmContentId): boolean { + //Result ncmContentStorageHas(NcmContentStorage* cs, bool* out, const NcmContentId* content_id) { + // u8 tmp=0; + // Result rc = serviceDispatchInOut(&cs->s, 7, *content_id, tmp); + // if (R_SUCCEEDED(rc) && out) *out = tmp & 1; + // return rc; + //} + const out = new Uint8Array(1); + this.#srv.dispatchInOut(7, contentId, out.buffer); + return Boolean(out[0] & 1); + } + + getPath(contentId: NcmContentId): string { + //Result ncmContentStorageGetPath(NcmContentStorage* cs, char* out_path, size_t out_size, const NcmContentId* content_id) { + // char tmpbuf[0x300]={0}; + // Result rc = serviceDispatchIn(&cs->s, 8, *content_id, + // .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out }, + // .buffers = { { tmpbuf, sizeof(tmpbuf) } }, + // ); + // if (R_SUCCEEDED(rc) && out_path) { + // strncpy(out_path, tmpbuf, out_size-1); + // out_path[out_size-1] = 0; + // } + // return rc; + //} + const out = new ArrayBuffer(0x300); + this.#srv.dispatchIn(8, contentId, { + bufferAttrs: [ + SfBufferAttr.FixedSize | SfBufferAttr.HipcPointer | SfBufferAttr.Out, + ], + buffers: [out], + }); + const nul = new Uint8Array(out).indexOf(0); + return new TextDecoder().decode(out.slice(0, nul)); + } + + getPlaceHolderPath(placeholderId: NcmPlaceHolderId): string { + //Result ncmContentStorageGetPlaceHolderPath(NcmContentStorage* cs, char* out_path, size_t out_size, const NcmPlaceHolderId* placeholder_id) { + // char tmpbuf[0x300]={0}; + // Result rc = serviceDispatchIn(&cs->s, 9, *placeholder_id, + // .buffer_attrs = { SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out }, + // .buffers = { { tmpbuf, sizeof(tmpbuf) } }, + // ); + // if (R_SUCCEEDED(rc) && out_path) { + // strncpy(out_path, tmpbuf, out_size-1); + // out_path[out_size-1] = 0; + // } + // return rc; + //} + const out = new ArrayBuffer(0x300); + this.#srv.dispatchIn(9, placeholderId, { + bufferAttrs: [ + SfBufferAttr.FixedSize | SfBufferAttr.HipcPointer | SfBufferAttr.Out, + ], + buffers: [out], + }); + const nul = new Uint8Array(out).indexOf(0); + return new TextDecoder().decode(out.slice(0, nul)); + } + + cleanupAllPlaceHolder() { + //Result ncmContentStorageCleanupAllPlaceHolder(NcmContentStorage * cs) { + // return _ncmCmdNoIO(& cs -> s, 10); + //} + this.#srv.dispatch(10); + } + + listPlaceHolder(maxCount = 20): NcmPlaceHolderId[] { + //Result ncmContentStorageListPlaceHolder(NcmContentStorage* cs, NcmPlaceHolderId* out_ids, s32 count, s32* out_count) { + // return serviceDispatchOut(&cs->s, 11, *out_count, + // .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + // .buffers = { { out_ids, count*sizeof(NcmPlaceHolderId) } }, + // ); + //} + const outIds = new ArrayBuffer(maxCount * 0x10); + const out = new Int32Array(1); + this.#srv.dispatchOut(11, out.buffer, { + bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.Out], + buffers: [outIds], + }); + const outCount = out[0]; + const rtn: NcmPlaceHolderId[] = new Array(outCount); + for (let i = 0; i < outCount; i++) { + rtn[i] = outIds.slice(i * 0x10, i * 0x10 + 0x10) as NcmPlaceHolderId; + } + return rtn; + } + + getContentCount(): number { + //Result ncmContentStorageGetContentCount(NcmContentStorage* cs, s32* out_count) { + // return serviceDispatchOut(&cs->s, 12, *out_count); + //} + const out = new Int32Array(1); + this.#srv.dispatchOut(12, out.buffer); + return out[0]; + } + + listContentId( + startOffset = 0, + maxCount = this.getContentCount(), + ): NcmContentId[] { + //Result ncmContentStorageListContentId(NcmContentStorage* cs, NcmContentId* out_ids, s32 count, s32* out_count, s32 start_offset) { + // return serviceDispatchInOut(&cs->s, 13, start_offset, *out_count, + // .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + // .buffers = { { out_ids, count*sizeof(NcmContentId) } }, + // ); + //} + const inData = new Int32Array([startOffset]); + const outIds = new ArrayBuffer(maxCount * 0x10); + const out = new Int32Array(1); + this.#srv.dispatchInOut(13, inData.buffer, out.buffer, { + bufferAttrs: [SfBufferAttr.HipcMapAlias | SfBufferAttr.Out], + buffers: [outIds], + }); + const outCount = out[0]; + const rtn: NcmContentId[] = new Array(outCount); + for (let i = 0; i < outCount; i++) { + rtn[i] = outIds.slice(i * 0x10, i * 0x10 + 0x10) as NcmContentId; + } + return rtn; + } + + getSizeFromContentId(contentId: NcmContentId): bigint { + //Result ncmContentStorageGetSizeFromContentId(NcmContentStorage* cs, s64* out_size, const NcmContentId* content_id) { + // return _ncmCmdInContentIdOutU64(&cs->s, content_id, (u64*)out_size, 14); + //} + const out = new BigInt64Array(1); + this.#srv.dispatchInOut(14, contentId, out.buffer); + return out[0]; + } + + disableForcibly() { + //Result ncmContentStorageDisableForcibly(NcmContentStorage* cs) { + // return _ncmCmdNoIO(&cs->s, 15); + //} + this.#srv.dispatch(15); + } + + get freeSpace(): bigint { + //Result ncmContentStorageGetFreeSpaceSize(NcmContentStorage* cs, s64* out_size) { + // if (hosversionBefore(2,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + // return _ncmCmdNoInOutU64(&cs->s, (u64*)out_size, 22); + //} + const out = new BigInt64Array(1); + this.#srv.dispatchOut(22, out.buffer); + return out[0]; + } + + get totalSpace(): bigint { + //Result ncmContentStorageGetTotalSpaceSize(NcmContentStorage* cs, s64* out_size) { + // if (hosversionBefore(2,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + // return _ncmCmdNoInOutU64(&cs->s, (u64*)out_size, 23); + //} + const out = new BigInt64Array(1); + this.#srv.dispatchOut(23, out.buffer); + return out[0]; + } + + flushPlaceHolder() { + //Result ncmContentStorageFlushPlaceHolder(NcmContentStorage* cs) { + // if (hosversionBefore(3,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + // return _ncmCmdNoIO(&cs->s, 24); + //} + this.#srv.dispatch(24); + } + + getSizeFromPlaceHolderId(placeholderId: NcmPlaceHolderId): bigint { + //Result ncmContentStorageGetSizeFromPlaceHolderId(NcmContentStorage* cs, s64* out_size, const NcmPlaceHolderId* placeholder_id) { + // if (hosversionBefore(4,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + // return _ncmCmdInPlaceHolderIdOutU64(&cs->s, placeholder_id, (u64*)out_size, 25); + //} + const out = new BigInt64Array(1); + this.#srv.dispatchInOut(25, placeholderId, out.buffer); + return out[0]; + } + + repairInvalidFileAttribute() { + //Result ncmContentStorageRepairInvalidFileAttribute(NcmContentStorage* cs) { + // if (hosversionBefore(4,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + // return _ncmCmdNoIO(&cs->s, 26); + //} + this.#srv.dispatch(26); + } +} diff --git a/packages/ncm/src/index.ts b/packages/ncm/src/index.ts new file mode 100644 index 00000000..b9d2ac1a --- /dev/null +++ b/packages/ncm/src/index.ts @@ -0,0 +1,38 @@ +import { ncm } from './service'; +import type { NcmStorageId } from './types'; + +export * from './types'; +export * from './content-storage'; +export * from './content-meta-database'; + +export function createContentStorage(storageId: NcmStorageId) { + //Result ncmCreateContentStorage(NcmStorageId storage_id) { + // return _ncmCmdInU8(&g_ncmSrv, storage_id, 0); + //} + const inData = new Uint8Array([storageId]); + ncm.dispatchIn(0, inData.buffer); +} + +export function createContentMetaDatabase(storageId: NcmStorageId) { + //Result ncmCreateContentMetaDatabase(NcmStorageId storage_id) { + // return _ncmCmdInU8(&g_ncmSrv, storage_id, 1); + //} + const inData = new Uint8Array([storageId]); + ncm.dispatchIn(1, inData.buffer); +} + +export function verifyContentStorage(storageId: NcmStorageId) { + //Result ncmVerifyContentStorage(NcmStorageId storage_id) { + // return _ncmCmdInU8(&g_ncmSrv, storage_id, 2); + //} + const inData = new Uint8Array([storageId]); + ncm.dispatchIn(2, inData.buffer); +} + +export function verifyContentMetaDatabase(storageId: NcmStorageId) { + //Result ncmVerifyContentMetaDatabase(NcmStorageId storage_id) { + // return _ncmCmdInU8(&g_ncmSrv, storage_id, 3); + //} + const inData = new Uint8Array([storageId]); + ncm.dispatchIn(3, inData.buffer); +} diff --git a/packages/ncm/src/service.ts b/packages/ncm/src/service.ts new file mode 100644 index 00000000..3bffa726 --- /dev/null +++ b/packages/ncm/src/service.ts @@ -0,0 +1 @@ +export const ncm = new Switch.Service('ncm'); diff --git a/packages/ncm/src/types.ts b/packages/ncm/src/types.ts new file mode 100644 index 00000000..48d67c85 --- /dev/null +++ b/packages/ncm/src/types.ts @@ -0,0 +1,85 @@ +/// StorageId +export enum NcmStorageId { + None = 0, ///< None + Host = 1, ///< Host + GameCard = 2, ///< GameCard + BuiltInSystem = 3, ///< BuiltInSystem + BuiltInUser = 4, ///< BuiltInUser + SdCard = 5, ///< SdCard + Any = 6, ///< Any +} + +/// ContentType +export enum NcmContentType { + Meta = 0, ///< Meta + Program = 1, ///< Program + Data = 2, ///< Data + Control = 3, ///< Control + HtmlDocument = 4, ///< HtmlDocument + LegalInformation = 5, ///< LegalInformation + DeltaFragment = 6, ///< DeltaFragment +} + +/// ContentMetaType +export enum NcmContentMetaType { + Unknown = 0x0, ///< Unknown + SystemProgram = 0x1, ///< SystemProgram + SystemData = 0x2, ///< SystemData + SystemUpdate = 0x3, ///< SystemUpdate + BootImagePackage = 0x4, ///< BootImagePackage + BootImagePackageSafe = 0x5, ///< BootImagePackageSafe + Application = 0x80, ///< Application + Patch = 0x81, ///< Patch + AddOnContent = 0x82, ///< AddOnContent + Delta = 0x83, ///< Delta + DataPatch = 0x84, ///< DataPatch +} +NcmContentMetaType; + +/// ContentMetaAttribute +export enum NcmContentMetaAttribute { + None = 0, ///< None + IncludesExFatDriver = 1 << 0, ///< IncludesExFatDriver + Rebootless = 1 << 1, ///< Rebootless + Compacted = 1 << 2, ///< Compacted +} + +/// ContentInstallType +export enum NcmContentInstallType { + Full = 0, ///< Full + FragmentOnly = 1, ///< FragmentOnly + Unknown = 7, ///< Unknown +} + +/// ContentMetaPlatform +export enum NcmContentMetaPlatform { + Nx = 0, ///< Nx +} + +/// ContentId +//typedef struct { +// u8 c[0x10]; ///< Id +//} NcmContentId; +declare const contentIdSymbol: unique symbol; +export type NcmContentId = ArrayBuffer & { + [contentIdSymbol]: void; +}; + +/// PlaceHolderId +//typedef struct { +// Uuid uuid; ///< UUID +//} NcmPlaceHolderId; +declare const placeHolderIdSymbol: unique symbol; +export type NcmPlaceHolderId = ArrayBuffer & { + [placeHolderIdSymbol]: void; +}; + +/// ContentMetaKey +//typedef struct { +// u64 id; ///< Id. +// u32 version; ///< Version. +// u8 type; ///< \ref NcmContentMetaType +// u8 install_type; ///< \ref NcmContentInstallType +// u8 padding[2]; ///< Padding. +//} NcmContentMetaKey; +export type NcmContentMetaKey = ArrayBuffer; diff --git a/packages/ncm/tsconfig.json b/packages/ncm/tsconfig.json new file mode 100644 index 00000000..60215a01 --- /dev/null +++ b/packages/ncm/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "dist", + "target": "es2022", + "declaration": true, + "sourceMap": true, + "moduleResolution": "Bundler", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "types": [ + "@nx.js/runtime" + ] + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/packages/ncm/typedoc.json b/packages/ncm/typedoc.json new file mode 100644 index 00000000..291328c8 --- /dev/null +++ b/packages/ncm/typedoc.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "extends": ["../../typedoc.json"], + "name": "@nx.js/ncm", + "entryPoints": ["./src/index.ts"], + "out": "docs" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 576dd3a1..4df7da27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -464,6 +464,9 @@ importers: '@nx.js/http': specifier: workspace:* version: link:../packages/http + '@nx.js/ncm': + specifier: workspace:* + version: link:../packages/ncm '@nx.js/repl': specifier: workspace:* version: link:../packages/repl @@ -580,6 +583,22 @@ importers: specifier: ^2.0.5 version: 2.0.5 + packages/ncm: + dependencies: + '@nx.js/constants': + specifier: ^0.3.0 + version: link:../constants + devDependencies: + '@nx.js/runtime': + specifier: workspace:* + version: link:../runtime + vite: + specifier: ^5.4.2 + version: 5.4.2 + vitest: + specifier: ^2.0.5 + version: 2.0.5 + packages/nro: dependencies: '@nx.js/patch-nacp':