@jjlmoya/utils-games-development 1.50.0 → 1.52.0
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.
- package/package.json +1 -1
- package/src/category/index.ts +3 -1
- package/src/entries.ts +8 -1
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/audioLoopPointFinder/audio-loop-point-finder.css +322 -0
- package/src/tool/audioLoopPointFinder/audio-player.ts +84 -0
- package/src/tool/audioLoopPointFinder/bibliography.astro +6 -0
- package/src/tool/audioLoopPointFinder/bibliography.ts +16 -0
- package/src/tool/audioLoopPointFinder/client.ts +262 -0
- package/src/tool/audioLoopPointFinder/component.astro +147 -0
- package/src/tool/audioLoopPointFinder/dom-utils.ts +55 -0
- package/src/tool/audioLoopPointFinder/entry.ts +27 -0
- package/src/tool/audioLoopPointFinder/i18n/de.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/en.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/es.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/fr.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/id.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/it.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/ja.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/ko.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/nl.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/pl.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/pt.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/ru.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/sv.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/tr.ts +211 -0
- package/src/tool/audioLoopPointFinder/i18n/zh.ts +211 -0
- package/src/tool/audioLoopPointFinder/index.ts +11 -0
- package/src/tool/audioLoopPointFinder/logic.test.ts +72 -0
- package/src/tool/audioLoopPointFinder/logic.ts +214 -0
- package/src/tool/audioLoopPointFinder/metadata-parser.ts +94 -0
- package/src/tool/audioLoopPointFinder/seo.astro +15 -0
- package/src/tool/audioLoopPointFinder/ui.ts +42 -0
- package/src/tool/audioLoopPointFinder/waveform-renderer.ts +53 -0
- package/src/tool/saveFileEditor/bibliography.astro +6 -0
- package/src/tool/saveFileEditor/bibliography.ts +12 -0
- package/src/tool/saveFileEditor/component.astro +351 -0
- package/src/tool/saveFileEditor/entry.ts +28 -0
- package/src/tool/saveFileEditor/game-save-file-editor.css +250 -0
- package/src/tool/saveFileEditor/i18n/de.ts +182 -0
- package/src/tool/saveFileEditor/i18n/en.ts +182 -0
- package/src/tool/saveFileEditor/i18n/es.ts +182 -0
- package/src/tool/saveFileEditor/i18n/fr.ts +182 -0
- package/src/tool/saveFileEditor/i18n/id.ts +182 -0
- package/src/tool/saveFileEditor/i18n/it.ts +182 -0
- package/src/tool/saveFileEditor/i18n/ja.ts +182 -0
- package/src/tool/saveFileEditor/i18n/ko.ts +182 -0
- package/src/tool/saveFileEditor/i18n/nl.ts +182 -0
- package/src/tool/saveFileEditor/i18n/pl.ts +182 -0
- package/src/tool/saveFileEditor/i18n/pt.ts +182 -0
- package/src/tool/saveFileEditor/i18n/ru.ts +182 -0
- package/src/tool/saveFileEditor/i18n/sv.ts +182 -0
- package/src/tool/saveFileEditor/i18n/tr.ts +182 -0
- package/src/tool/saveFileEditor/i18n/zh.ts +182 -0
- package/src/tool/saveFileEditor/index.ts +11 -0
- package/src/tool/saveFileEditor/logic.test.ts +69 -0
- package/src/tool/saveFileEditor/logic.ts +139 -0
- package/src/tool/saveFileEditor/seo.astro +15 -0
- package/src/tool/saveFileEditor/ui.ts +24 -0
- package/src/tools.ts +5 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export type EncryptionMethod = 'base64' | 'xor' | 'raw';
|
|
2
|
+
|
|
3
|
+
export interface ObfuscationOptions {
|
|
4
|
+
method: EncryptionMethod;
|
|
5
|
+
xorKey: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ParseResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
data: unknown | null;
|
|
11
|
+
error?: string;
|
|
12
|
+
detectedMethod?: EncryptionMethod;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function strToUtf8Bytes(str: string): Uint8Array {
|
|
16
|
+
return new TextEncoder().encode(str);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function utf8BytesToStr(bytes: Uint8Array): string {
|
|
20
|
+
return new TextDecoder().decode(bytes);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function bytesToBase64(bytes: Uint8Array): string {
|
|
24
|
+
let binary = '';
|
|
25
|
+
const len = bytes.byteLength;
|
|
26
|
+
for (let i = 0; i < len; i++) {
|
|
27
|
+
binary += String.fromCharCode(bytes[i]!);
|
|
28
|
+
}
|
|
29
|
+
return btoa(binary);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function base64ToBytes(base64Str: string): Uint8Array {
|
|
33
|
+
const cleanStr = base64Str.trim().replace(/\s+/g, '');
|
|
34
|
+
const binary = atob(cleanStr);
|
|
35
|
+
const bytes = new Uint8Array(binary.length);
|
|
36
|
+
for (let i = 0; i < binary.length; i++) {
|
|
37
|
+
bytes[i] = binary.charCodeAt(i);
|
|
38
|
+
}
|
|
39
|
+
return bytes;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function applyXor(data: Uint8Array, keyStr: string): Uint8Array {
|
|
43
|
+
if (!keyStr) return data;
|
|
44
|
+
const keyBytes = strToUtf8Bytes(keyStr);
|
|
45
|
+
if (keyBytes.length === 0) return data;
|
|
46
|
+
|
|
47
|
+
const result = new Uint8Array(data.length);
|
|
48
|
+
for (let i = 0; i < data.length; i++) {
|
|
49
|
+
result[i] = data[i]! ^ keyBytes[i % keyBytes.length]!;
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getDecodedString(trimmed: string, options: ObfuscationOptions): string {
|
|
55
|
+
if (options.method === 'raw') return trimmed;
|
|
56
|
+
const bytes = base64ToBytes(trimmed);
|
|
57
|
+
if (options.method === 'base64') return utf8BytesToStr(bytes);
|
|
58
|
+
if (options.method === 'xor') {
|
|
59
|
+
return utf8BytesToStr(applyXor(bytes, options.xorKey));
|
|
60
|
+
}
|
|
61
|
+
return trimmed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function decodeSaveData(rawContent: string, options: ObfuscationOptions): ParseResult {
|
|
65
|
+
const trimmed = rawContent.trim();
|
|
66
|
+
if (!trimmed) {
|
|
67
|
+
return { success: false, data: null, error: 'Empty file' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const decodedText = getDecodedString(trimmed, options);
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
data: JSON.parse(decodedText),
|
|
75
|
+
detectedMethod: options.method,
|
|
76
|
+
};
|
|
77
|
+
} catch (err: unknown) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
data: null,
|
|
81
|
+
error: err instanceof Error ? err.message : String(err),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function encodeSaveData(data: unknown, options: ObfuscationOptions): string {
|
|
87
|
+
const jsonStr = JSON.stringify(data, null, 2);
|
|
88
|
+
|
|
89
|
+
if (options.method === 'raw') {
|
|
90
|
+
return jsonStr;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const utf8Bytes = strToUtf8Bytes(jsonStr);
|
|
94
|
+
|
|
95
|
+
if (options.method === 'base64') {
|
|
96
|
+
return bytesToBase64(utf8Bytes);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (options.method === 'xor') {
|
|
100
|
+
const xorBytes = applyXor(utf8Bytes, options.xorKey);
|
|
101
|
+
return bytesToBase64(xorBytes);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return jsonStr;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function detectFormat(content: string, xorKey: string = ''): EncryptionMethod {
|
|
108
|
+
const trimmed = content.trim();
|
|
109
|
+
if (!trimmed) return 'raw';
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
JSON.parse(trimmed);
|
|
113
|
+
return 'raw';
|
|
114
|
+
} catch {
|
|
115
|
+
void 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (xorKey) {
|
|
119
|
+
try {
|
|
120
|
+
const bytes = base64ToBytes(trimmed);
|
|
121
|
+
const text = utf8BytesToStr(applyXor(bytes, xorKey));
|
|
122
|
+
JSON.parse(text);
|
|
123
|
+
return 'xor';
|
|
124
|
+
} catch {
|
|
125
|
+
void 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const bytes = base64ToBytes(trimmed);
|
|
131
|
+
const text = utf8BytesToStr(bytes);
|
|
132
|
+
JSON.parse(text);
|
|
133
|
+
return 'base64';
|
|
134
|
+
} catch {
|
|
135
|
+
void 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return 'raw';
|
|
139
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { saveFileEditor } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const loader = saveFileEditor.i18n[locale] ?? saveFileEditor.i18n.en;
|
|
12
|
+
const content = loader ? await loader() : null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content && content.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface SaveFileEditorUI extends Record<string, string> {
|
|
2
|
+
title: string;
|
|
3
|
+
subtitle: string;
|
|
4
|
+
dropSaveFile: string;
|
|
5
|
+
orSelectFile: string;
|
|
6
|
+
encryptionMethod: string;
|
|
7
|
+
methodBase64: string;
|
|
8
|
+
methodXor: string;
|
|
9
|
+
methodRaw: string;
|
|
10
|
+
xorKeyLabel: string;
|
|
11
|
+
xorKeyPlaceholder: string;
|
|
12
|
+
jsonRawTitle: string;
|
|
13
|
+
encodeAndDownload: string;
|
|
14
|
+
copyEncoded: string;
|
|
15
|
+
copiedNotice: string;
|
|
16
|
+
decodedKeysCount: string;
|
|
17
|
+
dataSize: string;
|
|
18
|
+
detectedFormat: string;
|
|
19
|
+
exportPreviewLabel: string;
|
|
20
|
+
decodePanelTitle: string;
|
|
21
|
+
exportPanelTitle: string;
|
|
22
|
+
decodeError: string;
|
|
23
|
+
bytesUnit: string;
|
|
24
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -3,9 +3,14 @@ import type { ToolDefinition } from './types';
|
|
|
3
3
|
import { STEAM_CAPSULE_GENERATOR_TOOL } from './tool/steamCapsuleGenerator';
|
|
4
4
|
import { ITCHIO_GAME_TESTER_TOOL } from './tool/itchioGameTester';
|
|
5
5
|
import { SPRITE_SHEET_PACKER_TOOL } from './tool/spriteSheetPacker';
|
|
6
|
+
import { AUDIO_LOOP_POINT_FINDER_TOOL } from './tool/audioLoopPointFinder';
|
|
7
|
+
import { SAVE_FILE_EDITOR_TOOL } from './tool/saveFileEditor';
|
|
6
8
|
|
|
7
9
|
export const ALL_TOOLS: ToolDefinition[] = [
|
|
8
10
|
STEAM_CAPSULE_GENERATOR_TOOL,
|
|
9
11
|
ITCHIO_GAME_TESTER_TOOL,
|
|
10
12
|
SPRITE_SHEET_PACKER_TOOL,
|
|
13
|
+
AUDIO_LOOP_POINT_FINDER_TOOL,
|
|
14
|
+
SAVE_FILE_EDITOR_TOOL,
|
|
11
15
|
];
|
|
16
|
+
|