Status: Draft Updated: 2026-06-28 Target: macOS 10.15+ (Catalina and later), Universal 2 (arm64 / x86_64)
Purpose: When the user double-clicks a
.narfile (a ZIP archive) or opens it with Ourin via "Open With", the app receives the file and extracts ghost / balloon / shell / plugin / package / calendar / language content to the appropriate directory according to the vocabulary defined ininstall.txt. This specification follows SSP conventions (drag-and-drop / double-click) while mandating UTF-8 as the default encoding, CP932 acceptance, and Zip Slip prevention.
0. Terminology and Prerequisites
- .nar: Ukagaka distribution package. The actual format is ZIP.
- install.txt: The install manifest placed at the top level of the archive (
type/directory/*.directoryand other fields). - Ourin base directory:
~/Documents/Ourin/(falls back to~/Library/Application Support/Ourin/when Documents is unavailable). - Character encoding: UTF-8 default, CP932 accepted (internally normalized to UTF-8).
1. Scope and Non-Goals
- In scope: Installation triggered by Finder double-click, "Open With", or drag-and-drop onto an Ourin window.
- Non-goals: Binary-level Windows compatibility reproduction or full Install UI replication. The focus is protocol (install.txt vocabulary) compatibility.
2. Dependencies and References
- Apple UTI/Document Types:
.narexported as a custom UTI conforming topublic.zip-archive``. Associated viaCFBundleDocumentTypes`; the app implements an open handler**. - NSApplicationDelegate: URL/path received via
application(_:openFiles:)/application(_:open:). - Ukadoc: Conforms to operational conventions for install.txt / distribution / network updates (
updates2.dau/delete.txt/drag-and-drop/double-click).
3. File Association (UTI/Document Types)
- Declare
jp.ourin.narin UTExportedTypeDeclarations withUTTypeConformsTo = public.zip-archiveand extensionnar. - Set
LSItemContentTypes = jp.ourin.narin CFBundleDocumentTypes (Role: Viewer or Editor). - Priority in case of conflict is controlled by LSHandlerRank.
4. Open Handler
- Implement both
NSApplicationDelegate.application(_:openFiles:)(multiple files) andapplication(_:open:)(URL) so the handler is called whether the app is already running or launches fresh. Bridge AppKit delegate even in SwiftUI configurations.
5. Archive Validation and Encoding Detection
- Verify zip compatibility by MIME/UTType and also check the leading record.
install.txtis read preferring UTF-8. On failure, retry with CP932 and normalize internally to UTF-8. BOM and CRLF are accepted.- When a
charsetdeclaration is present on the first non-comment line (eithercharset,<value>orcharset:<value>syntax), that encoding is used preferentially before falling back to auto-detection.
6. install.txt Vocabulary
- Required:
type,<kind>/directory,<install-name>(directorymay be omitted forsupplementandpackage) - Optional:
accept,<identifier>,*.directory,<bundled-name>(e.g.,balloon.directory,MyBalloon),*.source.directory, etc. - Recommended: Place
charset,UTF-8at the top of the file.
6.1 Accepted type Values and Install Destinations
| type value | Accepted spelling variants | Install destination (<base> = ~/Documents/Ourin) |
|---|---|---|
ghost |
— | <base>/ghost/<directory> |
balloon |
— | <base>/balloon/<directory> |
shell |
— | <base>/ghost/<accept>/shell/<directory> |
plugin |
— | <base>/plugin/<directory> |
headline |
— | <base>/headline/<directory> |
package |
— | <base>/package/<directory> |
supplement |
— | <base>/ghost/<accept>/ (directory is optional) |
calendar/skin |
calendar skin, calendarskin |
<base>/calendar/skin/<directory> |
calendar/plugin |
calendar plugin, calendarplugin |
<base>/calendar/plugin/<directory> |
calendar |
— (legacy form without skin/plugin distinction) | <base>/calendar/<directory> |
language |
— | <base>/language/<directory> |
calendar/skin and calendar/plugin are accepted regardless of case, spaces, or slashes (InstallTxtParser.swift → OurinPaths.installTarget()). The legacy calendar form (no sub-type distinction) is retained for backward compatibility. language corresponds to the language type in the UKADOC install.txt specification.
shell and supplement require the accept field to specify the target ghost's directory name. If accept is absent, an installTxtMissingKey("accept") error is raised.
6.2 refreshundeletemask Field
refreshundeletemask,pattern1:pattern2:...
- The UKADOC-specified delimiter is a colon (
:). This is the primary separator. - For compatibility with existing data, commas (
,) are also accepted as delimiters (InstallTxtParser.swift:106). - Values are parsed into the
refreshUndeleteMask: [String]array as a set of regex patterns. - Used in combination with
refresh,1. Whenrefresh,1is specified, paths matching any mask entry are excluded from pre-install deletion.
Example:
refresh,1
refreshundeletemask,savedata:*.sav:userconfig.txt
The above is parsed as three colon-delimited elements: savedata, *.sav, and userconfig.txt.
7. Extraction Policy (Safe Unzip and Placement)
- Safely extract to a temporary directory → interpret
install.txt→ copy to the install destination. - Excluded:
__MACOSX/,.DS_Store, andThumbs.dbare automatically excluded. - Case sensitivity: File names are handled strictly, with warnings on duplicate collisions.
8. Conflict and Update (accept/delete/homeurl)
- If the existing
{directory}is present: overwrite-update ifacceptmatches; suggest a rename if it differs. - If
delete.txtis present, remove unnecessary assets. Update distribution followshomeurl+updates2.dau.
9. Logging and Failure UX
- Log major phases (validation/parsing/extraction/post-processing) via
OSLog. - Representative errors:
InstallTxtMissing,UnsupportedType,ZipSlipDetected,NameConflict,DecodeFailed(sjis).
10. Security (Zip Slip / Quarantine)
- Zip Slip: Normalize paths before extraction and reject any write outside the target directory via
.., absolute paths, or symbolic links. - Quarantine attributes from downloaded files are treated as a normal flow since the action is user-initiated (double-click/drop).
11. Compatibility Notes (Windows/Case/Line-ending Differences)
- Existing Windows distributions may arrive with Shift_JIS encoding and
\\path separators. Ourin accepts the input and normalizes internally to UTF-8 / UNIX paths. - Both CRLF and LF line endings are accepted.
Appendix A. Info.plist Example
<!-- Info.plist excerpt: UTI and Document Types -->
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeIdentifier</key><string>jp.ourin.nar</string>
<key>UTTypeDescription</key><string>Ukagaka NAR</string>
<key>UTTypeConformsTo</key>
<array><string>public.zip-archive</string></array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key><array><string>nar</string></array>
<key>public.mime-type</key><string>application/x-ukagaka-nar</string>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key><string>Ukagaka NAR</string>
<key>LSItemContentTypes</key>
<array><string>jp.ourin.nar</string></array>
<key>CFBundleTypeRole</key><string>Viewer</string>
</dict>
</array>
Appendix B. Success/Failure Flow
Finder(.nar) → Launch Services → Ourin(app open)
→ open handler
→ validate(zip) OK? → read install.txt → parse {type,directory,...}
→ resolve install path
→ safeExtract → copy/merge → post steps(readme/terms, switch)
→ success toast
Failure cases:
- no install.txt → error: InstallTxtMissing
- unsupported type → error: UnsupportedType
- zip slip → error: ZipSlipDetected
- directory conflict → user prompt(rename/overwrite)
Appendix C. Sample Structure
OURIN_NAR_INSTALL_1_0M/
NAR_INSTALL_1.0M_SPEC.md
NAR_INSTALL_1.0M_PLAN.md
sample/
Sources/OurinNarInstallerSample/
AppDelegate.swift
SampleNarInstaller.swift
InstallTxtParser.swift
ZipUtil.swift
Paths.swift
Info.plist (example)
Implementation Status
Last Updated: 2026-06-28
Implemented in Ourin
- [x] NAR file association: UTI declaration and association for
.narin Info.plist - [x] Double-click/D&D handling: openFiles handler in AppDelegate
- [x] ZIP extraction: Implemented in
ZipUtil.swift - [x] install.txt parsing: Implemented in
InstallTxtParser.swift - [x] Encoding auto-detection: UTF-8 and CP932 auto-detection implemented
- [x] Zip Slip prevention: Implemented in
ZipUtil.secureCopyTree() - [x] Type-based install:
OurinPaths.installTarget()resolves install destinations for ghost/balloon/shell/supplement/plugin/headline/package/calendar/calendar/skin/calendar/plugin/language - [x] Basic error handling: Install error detection and reporting implemented
- [ ] Conflict UI: Full UI for accept/delete/homeurl is not yet complete
- [x] Update feature:
updates2.dau/updates.txt/update.txtretrieval, Version 2/3 parsing, MD5 verification, and incremental-file / NAR application are implemented. Baseware updates are applied to a staged.app. - [x] Deletion feature:
delete.txtis fetched and applied safely during network updates (and supported for package cleanup).
Implemented Features
-
Package Reception - Double-click from Finder - Drag-and-drop onto the Ourin window - Launch via "Open With This Application"
-
Extraction and Validation - ZIP header validation (PK magic number) - Safe extraction to a temporary directory - install.txt presence check
-
install.txt Parsing -
typefield parsing (ghost, balloon, shell, supplement, plugin, headline, package, calendar/skin, calendar/plugin, calendar, language) -directoryand*.directoryparsing -refreshundeletemaskfield: colon-delimited per UKADOC; commas also accepted for compatibility - UTF-8/CP932 auto-detection and decoding -
Safe Placement - Zip Slip attack prevention (no escaping to parent directories) - Placement under
~/Documents/Ourin/- Type-based directory routing (ghost, balloon, shell, supplement, plugin, headline, package, calendar/skin, calendar/plugin, calendar, language) -
Error Handling - NAR format error (NotZip) - install.txt missing (InstallTxtNotFound) - Decode error (InstallTxtDecodeFailed) - Unsupported type (UnsupportedType) - Zip Slip detected (ZipSlipDetected) - Directory conflict (DirectoryConflict)
Not Yet Implemented
-
Conflict Resolution UI - Overwrite confirmation dialog via the
acceptfield - Rename/overwrite choices presented to the user -
Deletion UI - File removal UI for an uninstall workflow
-
Advanced Features - README/terms of use display - Automatic switch after install -
homeurlprocessing
Source File Locations
Ourin/NarInstall/LocalNarInstaller.swift: Installer main bodyOurin/NarInstall/InstallTxtParser.swift: install.txt parsingOurin/NarInstall/ZipUtil.swift: ZIP extraction utilityOurin/NarInstall/Paths.swift: Path resolution (OurinPaths)Ourin/NarInstall/NarInstallViewModel.swift: UI ViewModelOurin/NarInstall/NarInstallView.swift: SwiftUI install screen
Changelog
- 2026-08-14: Implemented MD5-verified incremental updates for
updates2.dau/updates.txtand safedelete.txtretrieval/removal, with traversal checks and regression tests. - 2026-06-28: Added §6.1 type accepted-values table (calendar/skin, calendar/plugin, calendar, language). Added §6.2 refreshundeletemask field delimiter specification (colon primary, comma compatible). Updated implementation status type list. Expanded from placeholder to full specification.
- 2025-10-20: Implementation status section added (Japanese original).
- 2025-07-28: Initial revision (NAR-INSTALL/1.0M).
出典と編集について
Ourin / Furin Lab・contributors。文書はCC BY-SA 4.0(第三者の個別表記を除く)。Web版は見出し・リンクの整備とローカルパスの一般化を行っています。更新基準日: 2026-10-30。
元資料: docs/NAR_INSTALL_1.0M_SPEC_en-us.md
ソース SHA-256: aae58b38f1e0d58c4d2830273ebd1abbe6308b0fa1598dfa768e05029a7499b5