README
This commit is contained in:
parent
c76b5be2a9
commit
27e1e66da5
88
README.md
88
README.md
@ -4,19 +4,62 @@ source for <https://sheetjs.com/tools/iwa-inspector>
|
||||
|
||||
`iwa-inspector` is a tool for inspecting iWork archives.
|
||||
|
||||
<https://oss.sheetjs.com/notes/iwa/> covers the high-level structure of files.
|
||||
This inspector performs the top-level extraction of messages and parses using
|
||||
extracted Protocol Buffer definitions.
|
||||
|
||||
## Usage
|
||||
|
||||
When a file is loaded, a table will display the messages in the file. (The site
|
||||
automatically fetches a sample file on load)
|
||||
The sections are separated by a light gray horizontal line and a light gray
|
||||
vertical line. Panels can be resized by click-dragging the line.
|
||||
|
||||
When a message is selected, the page will display the Protocol Buffers
|
||||
definition for the message as well as an inspector for the message and metadata.
|
||||
### Selecting a File
|
||||
|
||||
The file input element in the top-left corner of the page is limited to the IWA
|
||||
file types: `.numbers`, `.key`, and `.pages`.
|
||||
|
||||
The site automatically fetches a sample file on load.
|
||||
|
||||
### Message Table
|
||||
|
||||
The message table is shown just below the header bar. The column headers are:
|
||||
|
||||
| name | description |
|
||||
|:----------|:----------------------|
|
||||
| `id` | Message ID |
|
||||
| `type` | Numeric Message Type |
|
||||
| `message` | Absolute Message Type |
|
||||
| `path` | Location of Message |
|
||||
|
||||
The table can be sorted by clicking on the column headers.
|
||||
|
||||
### Searching for Messages
|
||||
|
||||
The search text box in the top-right corner of the page is a plaintext search
|
||||
over the parsed messages. Searches will match field names, string values and
|
||||
UUID fields of message type `.TSP.UUID`.
|
||||
|
||||
### Selecting a Message
|
||||
|
||||
When a row in the table is selected, the bottom-left panel will display the
|
||||
Protocol Buffers definition for the message and the bottom-right panel will
|
||||
display the parsed contents.
|
||||
|
||||
### Message Structure
|
||||
|
||||
The bottom-right panel shows the following information:
|
||||
|
||||
- "Message": parsed information following the message definition
|
||||
- "Metadata": parsed information from the message metadata
|
||||
- "Dependents": list of messages that list the current message as a dependency.
|
||||
|
||||
Clicking on a message name in the inspector will show the message definition in
|
||||
the left pane. A "Return" link returns to the base message definition.
|
||||
|
||||
Clicking on a `.TSP.Reference` ID will jump to the referenced message.
|
||||
|
||||
### Exporting Data
|
||||
|
||||
Right-clicking a custom message type will show a context menu with options to
|
||||
copy the raw byte representation (array of numbers) or parsed object (JSON).
|
||||
|
||||
@ -26,8 +69,41 @@ copy the raw byte representation (array of numbers) or parsed object (JSON).
|
||||
|
||||
`make build` generates the static site.
|
||||
|
||||
### Refreshing Protos and Messages
|
||||
## Refreshing Protos and Messages
|
||||
|
||||
`make deps` requires a SIP-disabled Intel Mac with Keynote + Numbers + Pages.
|
||||
|
||||
The last run was on 2023-06-26 against version 13.1
|
||||
The last run was on 2023-06-26 against version 13.1
|
||||
|
||||
## Protocol Buffer Details
|
||||
|
||||
Note that the `message` field is absolute. For example, `TSTArchives.proto`
|
||||
specifies the `.TST.TileStorage` as follows:
|
||||
|
||||
```proto
|
||||
package TST;
|
||||
|
||||
message TileStorage {
|
||||
message Tile {
|
||||
required uint32 tileid = 1;
|
||||
required .TSP.Reference tile = 2;
|
||||
}
|
||||
repeated .TST.TileStorage.Tile tiles = 1;
|
||||
optional uint32 tile_size = 2;
|
||||
optional bool should_use_wide_rows = 3;
|
||||
}
|
||||
```
|
||||
|
||||
The protobuf extractor rewrites the message names using the absolute form:
|
||||
|
||||
```proto
|
||||
message .TST.TileStorage {
|
||||
message Tile {
|
||||
required uint32 tileid = 1;
|
||||
required .TSP.Reference tile = 2;
|
||||
}
|
||||
repeated .TST.TileStorage.Tile tiles = 1;
|
||||
optional uint32 tile_size = 2;
|
||||
optional bool should_use_wide_rows = 3;
|
||||
}
|
||||
```
|
||||
|
@ -22,7 +22,7 @@
|
||||
"react-inspector": "6.0.1",
|
||||
"react-resizable-panels": "0.0.45",
|
||||
"react-toastify": "9.1.3",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "18.2.6",
|
||||
|
24
src/App.tsx
24
src/App.tsx
@ -165,7 +165,7 @@ const preparse = (id: any, message: string, f: ParsedFile, p: ProtoMap) => {
|
||||
/* .TSP.MessageInfo */
|
||||
if(m.object_references) m.$object_references = m.object_references.map((n: BigInt) => {
|
||||
/* create a fake reference for the inspector */
|
||||
var o: $_TSP_Reference = ({ identifier: n });
|
||||
const o: $_TSP_Reference = ({ identifier: n });
|
||||
Object.defineProperty(o, "PB_TYPE", {value: ".TSP.Reference", enumerable: false});
|
||||
return o;
|
||||
});
|
||||
@ -173,7 +173,7 @@ const preparse = (id: any, message: string, f: ParsedFile, p: ProtoMap) => {
|
||||
/* .TSP.FieldInfo */
|
||||
if(fi.object_references) fi.$object_references = fi.object_references.map((n: BigInt) => {
|
||||
/* create a fake reference for the inspector */
|
||||
var o: $_TSP_Reference = ({ identifier: n });
|
||||
const o: $_TSP_Reference = ({ identifier: n });
|
||||
Object.defineProperty(o, "PB_TYPE", {value: ".TSP.Reference", enumerable: false});
|
||||
return o;
|
||||
});
|
||||
@ -224,14 +224,15 @@ function App() {
|
||||
/* scroll to selected row */
|
||||
const tblScroll = (R: number) => {
|
||||
if(R == -1) return;
|
||||
var rowelt = document.getElementById(`tr-${R}`);
|
||||
var top = rowelt?.offsetTop || 0;
|
||||
const rowelt = document.getElementById(`tr-${R}`);
|
||||
const top = rowelt?.offsetTop || 0;
|
||||
if(tblRef.current) {
|
||||
let tbl = tblRef.current;
|
||||
if(top > tbl.scrollTop + tbl.clientHeight - (rowelt?.clientHeight||0) || top < tbl.scrollTop + (rowelt?.clientHeight||0)) tbl.scrollTop = Math.max(0, top - tbl.clientHeight/2 - (rowelt?.clientHeight||0)/2);
|
||||
}
|
||||
};
|
||||
|
||||
/* Sorting machinations */
|
||||
const onsort = (s: string) => {
|
||||
let d = false;
|
||||
console.log(sort == s, desc);
|
||||
@ -242,7 +243,6 @@ function App() {
|
||||
}
|
||||
file.tbl.sort((x: any, y: any) => (typeof x[s] == "number" ? x[s] - y[s] : String(x[s]).localeCompare(String(y[s]))) * (d ? -1 : 1));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if(id) tblScroll(file.tbl.findIndex(row => row["id"] == +id));
|
||||
}, [sort, desc]);
|
||||
@ -266,7 +266,7 @@ function App() {
|
||||
setMeta(meta);
|
||||
setDeps(file.tbl.filter(r => ((file.space[+r.id][0].parsedmeta?.object_references||[])?.indexOf(BigInt(row.id)) > -1)).map(v => {
|
||||
/* this copy uses non-enumerable fields */
|
||||
var o = {}; Object.entries(v).forEach(([k,v])=> Object.defineProperty(o, k, { enumerable: false, value: v })); return o;
|
||||
const o = {}; Object.entries(v).forEach(([k,v])=> Object.defineProperty(o, k, { enumerable: false, value: v })); return o;
|
||||
}));
|
||||
setId(String(row.id));
|
||||
tblScroll(R);
|
||||
@ -275,7 +275,7 @@ function App() {
|
||||
const rowclick = (row: any, R: number) => { doitRow(row, R, true); };
|
||||
/* helper for .TSP.Reference */
|
||||
const gotoRef = (id: string) => {
|
||||
var R = file.tbl.findIndex(t => +t.id == +id);
|
||||
const R = file.tbl.findIndex(t => +t.id == +id);
|
||||
if (R == -1) throw new Error(`Message ${id} not found`);
|
||||
doitRow(file.tbl[R], R, false);
|
||||
};
|
||||
@ -285,7 +285,7 @@ function App() {
|
||||
const pop = () => {
|
||||
const oldId = stack.pop()||"0";
|
||||
setStack([...stack]);
|
||||
var R = file.tbl.findIndex(t => +t.id == +oldId);
|
||||
const R = file.tbl.findIndex(t => +t.id == +oldId);
|
||||
if (R == -1) throw new Error(`Message ${oldId} not found`);
|
||||
doitRow(file.tbl[R], R);
|
||||
};
|
||||
@ -357,7 +357,7 @@ function App() {
|
||||
}
|
||||
function onClickId(){ gotoRef(menuId); }
|
||||
function onClickCopyByteArray({ props }: {props: MenuProps}){
|
||||
var _data = props?.data?.PB_RAW?.data || (+props.id == +id) && file.space[+id][0].data;
|
||||
const _data = props?.data?.PB_RAW?.data || (+props.id == +id) && file.space[+id][0].data;
|
||||
if(!_data) throw new Error("Could not find raw data");
|
||||
navigator.clipboard.writeText("[" + [..._data].map(x => "0x" + x.toString(16).toUpperCase().padStart(2,"0")).join(", ") + "]");
|
||||
}
|
||||
@ -367,6 +367,8 @@ function App() {
|
||||
}
|
||||
function showProtoDef({props}: {props: MenuProps}) { selectProto(props.type); }
|
||||
function showXXD({props}: {props: MenuProps}) { console.log(xxd(props.data)); }
|
||||
|
||||
/* Inspector machinations */
|
||||
type NodeRendererProps = {
|
||||
depth: number;
|
||||
name: string;
|
||||
@ -438,11 +440,13 @@ function App() {
|
||||
return ( <a onClick={()=>gotoRef(data.id)}><ObjectLabel name={data.id} data={data.message} isNonenumerable={isNonenumerable} /></a> );
|
||||
};
|
||||
|
||||
const showHelp = () => { toast.info(<a href="https://git.sheetjs.com/sheetjs/iwa-inspector" target="_blank">Click for more info</a>); }
|
||||
|
||||
return ( <>
|
||||
{/* header */}
|
||||
<div id="header" className="header" style={{display: "grid", gridTemplateColumns: "repeat(3, 1fr)", alignItems:"center"}}>
|
||||
<div style={{display: "flex", justifyContent: "flex-start"}}><input type="file" id="file" onChange={onChange} disabled={loading} accept=".numbers,.key,.pages"/></div>
|
||||
<div><b><a href="https://sheetjs.com">SheetJS</a> IWA Inspector</b></div>
|
||||
<div><b><a href="https://sheetjs.com">SheetJS</a> IWA Inspector</b> <b onClick={showHelp}>(?)</b></div>
|
||||
<div style={{display: "flex", justifyContent: "flex-end"}}><input type="text" value={search} placeholder="search for text" onChange={(e)=>setSearch(e.target.value)}/></div>
|
||||
</div>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user