Skip to content

Commit bfe490f

Browse files
committed
Init
1 parent 23c1ba4 commit bfe490f

11 files changed

Lines changed: 512 additions & 1 deletion

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
dist
3+
package-lock.json
4+
rcc/qresource/
5+
*.rcc

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,24 @@
1-
# Nekiro-s-Rcc-Editor
1+
# Nekiro's Rcc Editor
2+
3+
You need nodejs and npm installed.
4+
5+
### How to use?
6+
7+
- Navigate to project directory
8+
- npm i
9+
- npm run start
10+
11+
To compile write
12+
13+
- npm run dist
14+
15+
Compiled binaries are located in "dist" directory.
16+
By default both win32 and x64 versions are being compiled
17+
18+
If you don't want to compile, download latest version from releases.
19+
20+
Enjoy.
21+
22+
## Donate
23+
24+
If you like my work and respect my time, consider becoming [Github Sponsor](https://github.com/sponsors/nekiro).

assets/icon.ico

7.33 KB
Binary file not shown.

css/style.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
body {
2+
margin: 0;
3+
padding: 0;
4+
height: 100vh;
5+
background-color: #222831;
6+
display: grid;
7+
grid-template-columns: 1fr 4fr;
8+
user-select: none;
9+
}
10+
11+
div::-webkit-scrollbar-track {
12+
background-color: #222831;
13+
border: 1px solid black;
14+
border-right: 1px transparent;
15+
}
16+
div::-webkit-scrollbar {
17+
width: 12px;
18+
}
19+
div::-webkit-scrollbar-thumb {
20+
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
21+
background-color: #393e46;
22+
}
23+
24+
.list {
25+
background-color: #393e46;
26+
border: 1px solid black;
27+
overflow: auto;
28+
overflow-x: hidden;
29+
}
30+
.list button {
31+
text-align: end;
32+
width: 100%;
33+
min-height: 30px;
34+
max-height: 200px;
35+
border: transparent;
36+
border-bottom: 1px solid black;
37+
background: transparent;
38+
color: lightgray;
39+
display: grid;
40+
grid-template-columns: 80% 20%;
41+
align-items: center;
42+
column-gap: 10px;
43+
justify-content: center;
44+
}
45+
.list button:hover {
46+
color: gold;
47+
background-color: #222831;
48+
}
49+
.focused {
50+
background-color: #222831 !important;
51+
color: gold !important;
52+
}
53+
54+
.preview {
55+
background-color: #393e46;
56+
padding: 25px;
57+
}
58+
59+
.center-container {
60+
display: flex;
61+
justify-content: center;
62+
align-items: center;
63+
}
64+
.miniature-holder {
65+
overflow: auto;
66+
margin-left: 30px;
67+
margin-right: 30px;
68+
}
69+
.miniature {
70+
width: auto;
71+
height: auto;
72+
max-width: 40px;
73+
justify-self: start;
74+
max-height: 30px;
75+
object-fit: contain;
76+
}

html/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta
6+
http-equiv="Content-Security-Policy"
7+
content="default-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'"
8+
/>
9+
<link rel="stylesheet" href="../css/style.css" />
10+
</head>
11+
<body>
12+
<div class="list"></div>
13+
<div class="center-container miniature-holder">
14+
<img
15+
src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
16+
class="preview"
17+
/>
18+
</div>
19+
<script src="../js/mainRenderer.js"></script>
20+
</body>
21+
</html>

js/ipcMain.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const { ipcMain } = require("electron");
2+
const { getImageByIndex, replaceImage } = require("./reader");
3+
4+
ipcMain.on("get-image-data", (event, index) => {
5+
event.reply("update-preview", getImageByIndex(index));
6+
});
7+
8+
ipcMain.on("replace-image", async (event, obj) => {
9+
const data = await replaceImage(obj.index, obj.path);
10+
if (data) {
11+
event.reply("update-preview", data);
12+
event.reply("update-miniature", { index: obj.index, data });
13+
}
14+
});

js/mainRenderer.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//renderer process
2+
const { ipcRenderer } = require("electron");
3+
4+
const preview = document.querySelector(".preview");
5+
const list = document.querySelector(".list");
6+
let focused = null;
7+
8+
preview.addEventListener("drop", (event) => {
9+
event.preventDefault();
10+
event.stopPropagation();
11+
12+
if (!focused) {
13+
return;
14+
}
15+
16+
ipcRenderer.send("replace-image", {
17+
index: parseInt(focused.id.split("-")[1]),
18+
path: fevent.dataTransfer.files[0].path,
19+
});
20+
});
21+
22+
preview.addEventListener("dragover", (e) => {
23+
e.preventDefault();
24+
e.stopPropagation();
25+
});
26+
27+
ipcRenderer.on("update-preview", (event, data) => {
28+
preview.src = `data:image/png;base64,${Buffer.from(data).toString("base64")}`;
29+
});
30+
31+
ipcRenderer.on("update-miniature", (event, { index, data }) => {
32+
list.querySelector(
33+
`#btn-${index} > img`
34+
).src = `data:image/png;base64,${Buffer.from(data).toString("base64")}`;
35+
});
36+
37+
ipcRenderer.on("populate-list", (event, images) => {
38+
focused = null;
39+
40+
while (list.firstChild) {
41+
list.removeChild(list.firstChild);
42+
}
43+
44+
for (let index = 0; index < images.length; ++index) {
45+
const image = images[index];
46+
47+
if (!image.isImage) {
48+
continue;
49+
}
50+
51+
let btn = document.createElement("button");
52+
btn.innerText = `${image.name}`;
53+
btn.id = `btn-${index}`;
54+
btn.onclick = (event) => {
55+
if (focused == event.target) {
56+
return;
57+
}
58+
59+
if (focused) {
60+
focused.classList.remove("focused");
61+
}
62+
63+
focused = event.target;
64+
focused.classList.add("focused");
65+
ipcRenderer.send("get-image-data", index);
66+
};
67+
68+
let img = document.createElement("img");
69+
img.className = "miniature";
70+
img.src = `data:image/png;base64,${Buffer.from(image.data).toString(
71+
"base64"
72+
)}`;
73+
btn.appendChild(img);
74+
75+
list.appendChild(btn);
76+
}
77+
});

js/reader.js

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
const { dialog, BrowserWindow, app } = require("electron");
2+
const path = require("path");
3+
const util = require("util");
4+
const execFile = util.promisify(require("child_process").execFile);
5+
6+
const Promise = require("bluebird");
7+
const fs = Promise.promisifyAll(require("fs-extra"));
8+
9+
let loadedFilePath = null;
10+
let images = [];
11+
12+
const getResourcePath = () => {
13+
if (app.isPackaged) {
14+
return process.resourcesPath;
15+
} else {
16+
return ".";
17+
}
18+
};
19+
20+
const getFiles = async (path = "./") => {
21+
const entries = await fs.readdir(path, { withFileTypes: true });
22+
const files = entries
23+
.filter((file) => !file.isDirectory())
24+
.map((file) => ({ ...file, path: path + file.name }));
25+
26+
const folders = entries.filter((folder) => folder.isDirectory());
27+
for (const folder of folders) {
28+
files.push(...(await getFiles(`${path}/${folder.name}/`)));
29+
}
30+
return files;
31+
};
32+
33+
const loadRcc = async (filePath) => {
34+
const localPath = `${path.resolve(getResourcePath(), "rcc")}`;
35+
36+
// clear previous images
37+
images = [];
38+
39+
// delete res directory
40+
try {
41+
await fs.rmdir(`${localPath}/qresource`, { recursive: true });
42+
} catch {}
43+
44+
await fs.copyFile(filePath, `${localPath}/res.rcc`);
45+
46+
const result = await execFile(`${localPath}/rcc.exe`, ["--reverse"], {
47+
cwd: `${localPath}/`,
48+
});
49+
50+
// get directory content
51+
const files = await getFiles(`${localPath}/qresource/res/res.rcc`);
52+
for (const file of files) {
53+
const ext = path.extname(file.path);
54+
images.push({
55+
name: path.parse(file.name).name,
56+
path: path.relative(`${localPath}/qresource/res/res.rcc`, file.path),
57+
isImage: ext === ".png" || ext === ".jpg",
58+
data: Buffer.from(await fs.readFile(file.path, "binary"), "binary"),
59+
});
60+
}
61+
62+
// sort by name
63+
images.sort((a, b) => a.name.localeCompare(b.name));
64+
65+
// cleanup
66+
await fs.rmdir(`${localPath}/qresource`, { recursive: true });
67+
await fs.rm(`${localPath}/res.rcc`);
68+
69+
loadedFilePath = filePath;
70+
71+
BrowserWindow.getAllWindows()[0].webContents.send("populate-list", images);
72+
};
73+
74+
const extractToPng = async (directoryPath) => {
75+
if (images.length === 0) {
76+
dialog.showErrorBox("Error", "Nothing to extract.");
77+
return;
78+
}
79+
80+
for (const image of images) {
81+
if (image.isImage) {
82+
await fs.outputFileAsync(`${directoryPath}/${image.path}`, image.data);
83+
}
84+
}
85+
86+
dialog.showMessageBox(null, {
87+
message: `Png images extracted successfully. Extracted ${images.length} images.`,
88+
type: "info",
89+
});
90+
};
91+
92+
const saveRcc = async (filePath) => {
93+
if (images.length === 0) {
94+
return;
95+
}
96+
97+
filePath = filePath || loadedFilePath;
98+
99+
const localPath = `${path.resolve(getResourcePath(), "rcc")}`;
100+
101+
// create .qrc file
102+
let data = `<!DOCTYPE RCC><RCC version="1.0">\n<qresource>\n`;
103+
104+
for (const image of images) {
105+
data += `<file>${image.path}</file>\n`;
106+
}
107+
108+
data += `</qresource>\n</RCC>`;
109+
110+
await fs.outputFileAsync(`${localPath}/res/res.qrc`, data);
111+
112+
// dump images
113+
for (const image of images) {
114+
await fs.outputFileAsync(`${localPath}/res/${image.path}`, image.data);
115+
}
116+
117+
const result = await execFile(
118+
`${localPath}/rcc.exe`,
119+
[
120+
"--format-version",
121+
"1",
122+
"--binary",
123+
`./res/res.qrc`,
124+
"-o",
125+
"./res/res_output.rcc",
126+
],
127+
{
128+
cwd: `${localPath}/`,
129+
}
130+
);
131+
132+
await fs.move("./rcc/res/res_output.rcc", `${filePath}`, { overwrite: true });
133+
134+
// cleanup
135+
await fs.rmdir(`${localPath}/res`, { recursive: true });
136+
137+
dialog.showMessageBox(null, {
138+
message: `Rcc saved successfully.`,
139+
type: "info",
140+
});
141+
};
142+
143+
const replaceImage = async (index, filePath) => {
144+
const image = images[index];
145+
if (!image) {
146+
return null;
147+
}
148+
149+
image.data = Buffer.from(await fs.readFile(filePath, "binary"), "binary");
150+
return image.data;
151+
};
152+
153+
const getImageByIndex = (index) => {
154+
return images[index]?.data;
155+
};
156+
157+
module.exports = {
158+
loadRcc,
159+
saveRcc,
160+
replaceImage,
161+
getImageByIndex,
162+
extractToPng,
163+
};

0 commit comments

Comments
 (0)