";
36 | }
37 | old(...args);
38 | gameState.logbuf = [
39 | {
40 | color,
41 | log: `[${new Date().toISOString()}]: ${str}`,
42 | },
43 | ];
44 | };
45 | }
46 | proxyConsole("error", "var(--error)");
47 | proxyConsole("warn", "var(--warning)");
48 | proxyConsole("log", "var(--fg)");
49 | proxyConsole("info", "var(--info)");
50 | proxyConsole("debug", "var(--fg6)");
51 |
52 | function hookfmod() {
53 | let contexts: AudioContext[] = [];
54 | let ctx = AudioContext;
55 | (AudioContext as any) = function() {
56 | let context = new ctx();
57 |
58 | contexts.push(context);
59 | return context;
60 | };
61 |
62 | window.addEventListener("focus", async () => {
63 | for (let context of contexts) {
64 | try {
65 | await context.resume();
66 | } catch { }
67 | }
68 | });
69 | window.addEventListener("blur", async () => {
70 | for (let context of contexts) {
71 | try {
72 | await context.suspend();
73 | } catch { }
74 | }
75 | });
76 | }
77 | hookfmod();
78 |
79 | if (import.meta.env.PROD) {
80 | try {
81 | if (!crossOriginIsolated) {
82 | console.log("not crossoriginisolated: using service worker");
83 | document.open();
84 | document.write("installing service worker, please wait...\n");
85 | document.close();
86 | setTimeout(() => location.reload(), 3000);
87 | }
88 | const registration = await navigator.serviceWorker.register("/sw.js", {
89 | scope: "/",
90 | });
91 |
92 | if (registration.installing) {
93 | console.log("Service worker installing");
94 | registration.waiting.addEventListener("statechange", (e) => {
95 | if (!crossOriginIsolated) {
96 | console.log("not crossoriginisolated, reloading");
97 | setTimeout(() => location.reload(), 1000);
98 | }
99 | });
100 | } else if (registration.waiting) {
101 | console.log("Service worker installed");
102 |
103 | } else if (registration.active) {
104 |
105 | }
106 | } catch (error) {
107 | console.error(`Registration failed with ${error}`);
108 | }
109 | } else {
110 | console.log("dev build: won't register service worker");
111 | }
112 | const wasm = await eval(`import("/_framework/dotnet.js")`);
113 | const dotnet = wasm.dotnet;
114 | (window as any).wasm = wasm;
115 | let exports: any;
116 |
117 | // the funny custom rsa
118 | // https://github.com/MercuryWorkshop/wispcraft/blob/main/src/connection/crypto.ts
119 | function encryptRSA(data: Uint8Array, n: bigint, e: bigint): Uint8Array {
120 | const modExp = (base: bigint, exp: bigint, mod: bigint) => {
121 | let result = 1n;
122 | base = base % mod;
123 | while (exp > 0n) {
124 | if (exp % 2n === 1n) {
125 | result = (result * base) % mod;
126 | }
127 | exp = exp >> 1n;
128 | base = (base * base) % mod;
129 | }
130 | return result;
131 | };
132 | // thank you jippity
133 | const pkcs1v15Pad = (messageBytes: Uint8Array, n: bigint) => {
134 | const messageLength = messageBytes.length;
135 | const nBytes = Math.ceil(n.toString(16).length / 2);
136 |
137 | if (messageLength > nBytes - 11) {
138 | throw new Error("Message too long for RSA encryption");
139 | }
140 |
141 | const paddingLength = nBytes - messageLength - 3;
142 | const padding = Array(paddingLength).fill(0xff);
143 |
144 | return BigInt(
145 | "0x" +
146 | [
147 | "00",
148 | "02",
149 | ...padding.map((byte) => byte.toString(16).padStart(2, "0")),
150 | "00",
151 | ...Array.from(messageBytes).map((byte: any) =>
152 | byte.toString(16).padStart(2, "0")
153 | ),
154 | ].join("")
155 | );
156 | };
157 | const paddedMessage = pkcs1v15Pad(data, n);
158 | let int = modExp(paddedMessage, e, n);
159 |
160 | let hex = int.toString(16);
161 | if (hex.length % 2) {
162 | hex = "0" + hex;
163 | }
164 |
165 | // ????
166 | return new Uint8Array(
167 | Array.from(hex.match(/.{2}/g) || []).map((byte) => parseInt(byte, 16))
168 | );
169 | }
170 |
171 | export const realFetch = window.fetch;
172 | export async function preInit() {
173 | console.debug("initializing dotnet");
174 | const runtime = await dotnet
175 | .withConfig({
176 | pthreadPoolInitialSize: 16,
177 | })
178 | .withEnvironmentVariable("MONO_SLEEP_ABORT_LIMIT", "99999")
179 | .create();
180 |
181 | console.log("loading libcurl");
182 | await libcurl.load_wasm(
183 | "https://cdn.jsdelivr.net/npm/libcurl.js@0.6.20/libcurl.wasm"
184 | );
185 | libcurl.set_websocket(store.wisp);
186 |
187 | useChange([store.wisp], () => libcurl.set_websocket(store.wisp));
188 |
189 | window.WebSocket = new Proxy(WebSocket, {
190 | construct(t, a, n) {
191 | if (a[0] === store.wisp) return Reflect.construct(t, a, n);
192 |
193 | return new libcurl.WebSocket(...a);
194 | },
195 | });
196 | window.fetch = libcurl.fetch;
197 |
198 | const config = runtime.getConfig();
199 | exports = await runtime.getAssemblyExports(config.mainAssemblyName);
200 | window.exports = exports;
201 |
202 | runtime.setModuleImports("interop.js", {
203 | encryptrsa: (
204 | publicKeyModulusHex: string,
205 | publicKeyExponentHex: string,
206 | data: Uint8Array
207 | ) => {
208 | let modulus = BigInt("0x" + publicKeyModulusHex);
209 | let exponent = BigInt("0x" + publicKeyExponentHex);
210 | let encrypted = encryptRSA(data, modulus, exponent);
211 | return new Uint8Array(encrypted);
212 | },
213 | });
214 |
215 | runtime.setModuleImports("depot.js", {
216 | newqr: (qr: string) => {
217 | console.log("QR DATA" + qr);
218 | gameState.qr = qr;
219 | },
220 | });
221 |
222 | (self as any).wasm = {
223 | Module: dotnet.instance.Module,
224 | dotnet,
225 | runtime,
226 | config,
227 | exports,
228 | };
229 |
230 | console.debug("PreInit...");
231 | await runtime.runMain();
232 | await exports.Program.PreInit();
233 | console.debug("dotnet initialized");
234 |
235 | // if (await exports.Program.InitSteamSaved() == 0) {
236 | // gameState.loginstate = 2;
237 | // }
238 |
239 | gameState.ready = true;
240 | }
241 |
242 | export async function initSteam(
243 | username: string | null,
244 | password: string | null,
245 | qr: boolean
246 | ) {
247 | return await exports.Program.InitSteam(username, password, qr);
248 | }
249 | export async function downloadApp() {
250 | return await exports.Program.DownloadApp();
251 | }
252 |
253 | export async function play() {
254 | gameState.playing = true;
255 |
256 | const before = performance.now();
257 | console.debug("Init...");
258 | await exports.Program.Init(screen.width, screen.height);
259 | const after = performance.now();
260 | console.debug(`Init : ${(after - before).toFixed(2)}ms`);
261 |
262 | console.debug("MainLoop...");
263 | const main = async () => {
264 | const before = performance.now();
265 | const ret = await exports.Program.MainLoop();
266 | const after = performance.now();
267 |
268 | gameState.timebuf.add(after - before);
269 |
270 | if (!ret) {
271 | console.debug("Cleanup...");
272 |
273 | gameState.timebuf.clear();
274 |
275 | await exports.Program.Cleanup();
276 | gameState.ready = false;
277 | gameState.playing = false;
278 |
279 | return;
280 | }
281 |
282 | requestAnimationFrame(main);
283 | };
284 | requestAnimationFrame(main);
285 | }
286 |
287 | useChange([gameState.playing], () => {
288 | try {
289 | if (gameState.playing) {
290 | // @ts-expect-error
291 | navigator.keyboard.lock();
292 | } else {
293 | // @ts-expect-error
294 | navigator.keyboard.unlock();
295 | }
296 | } catch (err) {
297 | console.log("keyboard lock error:", err);
298 | }
299 | });
300 |
301 | document.addEventListener("keydown", (e: KeyboardEvent) => {
302 | if (
303 | gameState.playing &&
304 | [
305 | "Space",
306 | "ArrowUp",
307 | "ArrowDown",
308 | "ArrowLeft",
309 | "ArrowRight",
310 | "Tab",
311 | ].includes(e.code)
312 | ) {
313 | e.preventDefault();
314 | }
315 | });
316 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { gameState, play, preInit, TIMEBUF_SIZE } from "./game";
2 |
3 | import { OpfsExplorer } from "./fs";
4 |
5 | import iconPlayArrow from "@ktibow/iconset-material-symbols/play-arrow";
6 | import iconFullscreen from "@ktibow/iconset-material-symbols/fullscreen";
7 | import iconFolderOpen from "@ktibow/iconset-material-symbols/folder-open";
8 | import { Dialog } from "./ui/Dialog";
9 | import { Button, Icon, Link } from "./ui/Button";
10 | export const NAME = "Terraria";
11 |
12 | export const Logo: Component<{}, {}> = function () {
13 | this.css = `
14 | display: flex;
15 | align-items: center;
16 | font-size: 1.5rem;
17 |
18 | font-family: Andy Bold;
19 |
20 | img {
21 | image-rendering: pixelated;
22 | -ms-interpolation-mode: nearest-neighbor;
23 | width: 3rem;
24 | height: 3rem;
25 | }
26 |
27 | .extras {
28 | align-self: start;
29 | padding: 0.25rem 0;
30 | font-size: 1rem;
31 | color: var(--fg6);
32 |
33 | display: flex;
34 | flex-direction: column;
35 | justify-content: space-between;
36 | }
37 | `;
38 | return (
39 |
40 |
41 |
terrarium
42 |
45 |
46 | );
47 | };
48 |
49 | const TopBar: Component<
50 | {
51 | canvas: HTMLCanvasElement;
52 | fsOpen: boolean;
53 | achievementsOpen: boolean;
54 | },
55 | { allowPlay: boolean; fps: HTMLElement }
56 | > = function () {
57 | this.css = `
58 | padding: 0.5em;
59 |
60 | display: flex;
61 | align-items: stretch;
62 | gap: 0.5rem;
63 |
64 | .group {
65 | display: flex;
66 | align-items: center;
67 | gap: 1rem;
68 | }
69 |
70 | .expand { flex: 1; }
71 |
72 | @media (max-width: 750px) {
73 | & {
74 | flex-direction: column;
75 | }
76 | .group {
77 | justify-content: space-evenly;
78 | }
79 | }
80 | `;
81 |
82 | useChange([gameState.ready, gameState.playing], () => {
83 | this.allowPlay = gameState.ready && !gameState.playing;
84 | });
85 |
86 | this.mount = () => {
87 | setInterval(() => {
88 | if (gameState.playing) {
89 | const avgFrametime =
90 | gameState.timebuf.toArray().reduce((acc, x) => acc + x, 0) /
91 | TIMEBUF_SIZE;
92 | const avgFps = (1000 / avgFrametime).toFixed(0);
93 | this.fps.innerText = "" + avgFps;
94 | }
95 | }, 1000);
96 | };
97 |
98 | return (
99 |
100 |
101 |
102 | {$if(
103 | use(gameState.playing),
104 |
105 | FPS:
106 |
107 | )}
108 |
109 |
110 |
111 | {/* this.achievementsOpen = true} icon="full" type="normal" disabled={false}>
112 |
113 | */}
114 | (this.fsOpen = true)}
116 | icon="full"
117 | type="normal"
118 | disabled={false}
119 | >
120 |
121 |
122 | {
124 | try {
125 | (navigator as any).keyboard.lock();
126 | await this.canvas.requestFullscreen({ navigationUI: "hide" });
127 | } catch {}
128 | }}
129 | icon="full"
130 | type="normal"
131 | disabled={use(gameState.playing, (x) => !x)}
132 | >
133 |
134 |
135 | {
137 | play();
138 | }}
139 | icon="left"
140 | type="primary"
141 | disabled={use(this.allowPlay, (x) => !x)}
142 | >
143 |
144 | Play
145 |
146 |
147 |
148 | );
149 | };
150 |
151 | const BottomBar: Component<{}, {}> = function () {
152 | this.css = `
153 | background: var(--bg);
154 | border-top: 2px solid var(--surface1);
155 | padding: 0.5rem;
156 | font-size: 0.8rem;
157 |
158 | display: flex;
159 | align-items: center;
160 | justify-content: space-between;
161 |
162 | span {
163 | text-align: center;
164 | }
165 |
166 | @media (max-width: 750px) {
167 | & {
168 | flex-direction: column;
169 | gap: 0.5rem;
170 | }
171 | }
172 | `;
173 |
174 | return (
175 |
176 |
177 | Ported by velzie
178 |
179 |
180 | All game assets and code belong to{" "}
181 | Re-Logic All rights reserved.
182 |
183 |
184 | );
185 | };
186 |
187 | const GameView: Component<{ canvas: HTMLCanvasElement }, {}> = function () {
188 | this.css = `
189 | aspect-ratio: 16 / 9;
190 | user-select: none;
191 | display: grid;
192 | grid-template-areas: "overlay";
193 | max-height: 90rem;
194 |
195 | div, canvas {
196 | grid-area: overlay;
197 | width: 100%;
198 | height: 100%;
199 | }
200 |
201 | div.started, canvas.stopped {
202 | display: none;
203 | }
204 |
205 | div {
206 | font-size: 2rem;
207 | font-weight: 570;
208 |
209 | display: flex;
210 | flex-direction: column;
211 | align-items: center;
212 | justify-content: center;
213 | }
214 |
215 | canvas:fullscreen {
216 | border: none;
217 | border-radius: 0;
218 | background: black;
219 | }
220 | `;
221 | const div = use(gameState.playing, (x) => (x ? "started" : "stopped"));
222 | const canvas = use(gameState.playing, (x) =>
223 | x ? "canvas started" : "canvas stopped"
224 | );
225 |
226 | this.mount = () => {
227 | // dotnet will immediately transfer the canvas to deputy thread, so this.mount is required
228 | preInit();
229 | };
230 |
231 | return (
232 |
233 |
Game not running.
234 |
e.preventDefault()}
239 | />
240 |
241 | );
242 | };
243 |
244 | export const LogView: Component<{}, {}> = function () {
245 | this.css = `
246 | height: 16rem;
247 | overflow: scroll;
248 | padding: 1em;
249 | background: var(--bg);
250 |
251 | font-family: var(--font-mono);
252 |
253 | ::-webkit-scrollbar {
254 | display: none;
255 | }
256 | `;
257 |
258 | const create = (color: string, log: string) => {
259 | const el = document.createElement("div");
260 | el.innerText = log;
261 | el.style.color = color;
262 | return el;
263 | };
264 |
265 | this.mount = () => {
266 | useChange([gameState.logbuf], () => {
267 | if (gameState.logbuf.length > 0) {
268 | for (const log of gameState.logbuf) {
269 | this.root.appendChild(create(log.color, log.log));
270 | }
271 | this.root.scrollTop = this.root.scrollHeight;
272 | }
273 | });
274 | };
275 |
276 | return
;
277 | };
278 |
279 | export const Main: Component<
280 | {},
281 | {
282 | canvas: HTMLCanvasElement;
283 | fsOpen: boolean;
284 | achievementsOpen: boolean;
285 | }
286 | > = function () {
287 | this.css = `
288 | width: 100%;
289 | height: 100%;
290 | background: url(/backdrop.png);
291 | color: var(--fg);
292 |
293 | display: flex;
294 | flex-direction: column;
295 | overflow: scroll;
296 |
297 | .main {
298 | flex: 1;
299 | display: flex;
300 | flex-direction: column;
301 | padding: 1rem 0;
302 |
303 | gap: 1em;
304 |
305 | margin: auto;
306 | width: min(1300px, calc(100% - 2rem));
307 | }
308 |
309 | .main h2 {
310 | margin: 0;
311 | }
312 | `;
313 |
314 | this.fsOpen = false;
315 | this.achievementsOpen = false;
316 |
317 | return (
318 |
319 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
335 |
336 |
337 | );
338 | };
339 |
--------------------------------------------------------------------------------
/src/splash.tsx:
--------------------------------------------------------------------------------
1 | import { LogView, NAME } from "./main";
2 | import { Button, Icon, Link } from "./ui/Button";
3 | import {
4 | copyFolder,
5 | countFolder,
6 | extractTar,
7 | PICKERS_UNAVAILABLE,
8 | rootFolder,
9 | TAR_TYPES,
10 | } from "./fs";
11 |
12 | import iconFolderOpen from "@ktibow/iconset-material-symbols/folder-open-outline";
13 | import iconFolderZip from "@ktibow/iconset-material-symbols/folder-zip-outline";
14 | import iconDownload from "@ktibow/iconset-material-symbols/download";
15 | import iconArchive from "@ktibow/iconset-material-symbols/archive";
16 | import iconEncrypted from "@ktibow/iconset-material-symbols/encrypted";
17 | import { downloadApp, gameState, initSteam, realFetch } from "./game";
18 | import { TextField } from "./ui/TextField";
19 | import { store } from "./store";
20 |
21 | const validateDirectory = async (directory: FileSystemDirectoryHandle) => {
22 | if (directory.name != "Content") {
23 | return "Directory name is not Content";
24 | }
25 | for (const child of ["Fonts", "Images", "Sounds"]) {
26 | try {
27 | await directory.getDirectoryHandle(child, { create: false });
28 | } catch {
29 | return `Failed to find subdirectory ${child}`;
30 | }
31 | }
32 | return "";
33 | };
34 |
35 | const Intro: Component<
36 | {
37 | "on:next": (
38 | type: "copy" | "download" | "simpledownload" | "extract"
39 | ) => void;
40 | },
41 | {}
42 | > = function () {
43 | this.css = `
44 | display: flex;
45 | flex-direction: column;
46 | gap: 1rem;
47 | height:100%;
48 | font-family: Andy Bold;
49 |
50 | .warning {
51 | color: var(--warning);
52 | }
53 | .error {
54 | color: var(--error);
55 | }
56 |
57 | .buttons {
58 | display: flex;
59 | align-self: end;
60 | gap: 1rem;
61 | width: 100%;
62 | div {
63 | width: 100%;
64 | }
65 | }
66 | `;
67 |
68 | return (
69 |
70 |
71 | This is a port of Terraria to
72 | the browser with WebAssembly. Frontend and build system is heavily based
73 | on r58's{" "}
74 |
75 | Celeste browser port
76 |
77 |
78 | Want to know how this was made? Check the{" "}
79 | writeup!
80 |
81 |
82 | {!import.meta.env.VITE_SIMPLE_DOWNLOAD ? (
83 |
84 | You will need to own Terraria to play this. Make sure you either own
85 | it on Steam, or have it downloaded and installed on your computer.
86 |
87 | ) : (
88 |
89 | THIS IS AN UNOFFICIAL DEPLOYMENT OF Terrarium. I (velzie) AM NOT
90 | HOSTING THE GAME CONTENT HERE. You can find the official deployment{" "}
91 | here
92 |
93 | )}
94 |
95 |
96 | A Mercury Workshop Project.
97 | Ported by velzie
98 |
99 |
100 | {PICKERS_UNAVAILABLE ? (
101 |
102 | Your browser does not support the{" "}
103 |
104 | File System Access API
105 |
106 | . You will be unable to copy your Terraria assets to play or use the
107 | upload features in the filesystem viewer. Please switch to a chromium
108 | based browser.
109 |
110 | ) : null}
111 |
112 |
113 |
114 | You will need to install Terraria to your browser with one of the
115 | following methods:
116 |
117 |
118 | {import.meta.env.VITE_SIMPLE_DOWNLOAD ? (
119 | this["on:next"]("simpledownload")}
121 | type="primary"
122 | icon="left"
123 | disabled={false}
124 | >
125 |
126 | Download Terraria
127 | /* @ts-expect-error */
128 | ) : (
129 | <>
130 | this["on:next"]("copy")}
132 | type="primary"
133 | icon="left"
134 | disabled={PICKERS_UNAVAILABLE}
135 | >
136 |
137 | {PICKERS_UNAVAILABLE
138 | ? "Copying local assets is unsupported"
139 | : "Copy local assets"}
140 |
141 | this["on:next"]("download")}
143 | type="primary"
144 | icon="left"
145 | disabled={false}
146 | >
147 |
148 | Download Assets from Steam
149 |
150 | this["on:next"]("extract")}
152 | type="primary"
153 | icon="left"
154 | disabled={false}
155 | >
156 |
157 | Upload Archive
158 |
159 | >
160 | )}
161 |
162 |
163 | );
164 | };
165 |
166 | const Progress: Component<{ percent: number }, {}> = function () {
167 | this.css = `
168 | background: var(--surface1);
169 | border-radius: 1rem;
170 | height: 1rem;
171 |
172 | .bar {
173 | background: var(--accent);
174 | border-radius: 1rem;
175 | height: 1rem;
176 | transition: width 250ms;
177 | }
178 | `;
179 |
180 | return (
181 |
184 | );
185 | };
186 |
187 | const Copy: Component<
188 | {
189 | "on:done": () => void;
190 | },
191 | {
192 | copying: boolean;
193 | status: string;
194 | percent: number;
195 | }
196 | > = function () {
197 | this.css = `
198 | display: flex;
199 | flex-direction: column;
200 | gap: 0.5rem;
201 | `;
202 |
203 | const opfs = async () => {
204 | const directory = await showDirectoryPicker();
205 | const res = await validateDirectory(directory);
206 | if (res) {
207 | this.status = res;
208 | return;
209 | }
210 |
211 | const max = await countFolder(directory);
212 | let cnt = 0;
213 | this.copying = true;
214 | const before = performance.now();
215 | await copyFolder(directory, rootFolder, (x) => {
216 | cnt++;
217 | this.percent = (cnt / max) * 100;
218 | console.debug(`copied ${x}: ${((cnt / max) * 100).toFixed(2)}`);
219 | });
220 | const after = performance.now();
221 | console.debug(`copy took ${(after - before).toFixed(2)}ms`);
222 |
223 | await new Promise((r) => setTimeout(r, 250));
224 | await rootFolder.getFileHandle(".ContentExists", { create: true });
225 | this["on:done"]();
226 | };
227 |
228 | return (
229 |
230 |
231 | Select your Terraria install's Content directory. It will be copied to
232 | browser storage and can be removed in the file manager.
233 |
234 |
235 | The Content directory for Steam installs of Terraria is usually located
236 | in one of these locations:
237 |
238 |
239 | ~/.steam/root/steamapps/common/Terraria
240 |
241 |
242 | C:\Program Files (x86)\Steam\steamapps\common\Terraria
243 |
244 |
245 |
246 | ~/Library/Application Support/Steam/steamapps/common/Terraria
247 |
248 |
249 |
250 |
251 | {$if(use(this.copying),
)}
252 |
258 |
259 | Select Terraria Content directory
260 |
261 | {$if(use(this.status),
{use(this.status)}
)}
262 |
263 | );
264 | };
265 |
266 | const Extract: Component<
267 | {
268 | "on:done": () => void;
269 | },
270 | {
271 | extracting: boolean;
272 | status: string;
273 | percent: number;
274 | }
275 | > = function () {
276 | this.css = `
277 | /* hacky */
278 | .center svg {
279 | transform: translateY(15%);
280 | }
281 | `;
282 |
283 | const opfs = async () => {
284 | const files = await showOpenFilePicker({
285 | excludeAcceptAllOption: true,
286 | types: TAR_TYPES,
287 | });
288 | const fileHandle = files[0];
289 |
290 | const file = await fileHandle.getFile();
291 |
292 | let parsedSize = 0;
293 | const fileSize = file.size;
294 |
295 | const stream = file.stream();
296 | const reader = stream.getReader();
297 | const self = this;
298 | let progressStream = new ReadableStream({
299 | async pull(controller) {
300 | const { value, done } = await reader.read();
301 |
302 | if (!value || done) {
303 | controller.close();
304 | } else {
305 | controller.enqueue(value);
306 |
307 | parsedSize += value.byteLength;
308 | self.percent = (parsedSize / fileSize) * 100;
309 | }
310 | },
311 | });
312 |
313 | this.extracting = true;
314 |
315 | if (fileHandle.name.endsWith(".gz"))
316 | progressStream = progressStream.pipeThrough(
317 | new DecompressionStream("gzip")
318 | );
319 | await extractTar(progressStream, rootFolder, (type, name) =>
320 | console.log(`untarred ${type} ${name}`)
321 | );
322 |
323 | this.extracting = false;
324 |
325 | await rootFolder.getFileHandle(".ContentExists", { create: true });
326 | this["on:done"]();
327 | };
328 |
329 | return (
330 |
331 |
332 | Select a {NAME} exported .tar archive of the root directory. You can
333 | create this on a browser with {NAME} already set-up by clicking the
334 | archive button ( ) in the filesystem explorer
335 | while in the root directory.
336 |
337 | {$if(use(this.extracting),
)}
338 |
344 |
345 | Select {NAME} archive
346 |
347 | {$if(use(this.status),
{use(this.status)}
)}
348 |
349 | );
350 | };
351 |
352 | const SimpleDownload: Component<
353 | {
354 | "on:done": () => void;
355 | },
356 | {
357 | extracting: boolean;
358 | status: string;
359 | percent: number;
360 | }
361 | > = function () {
362 | this.css = `
363 | /* hacky */
364 | .center svg {
365 | transform: translateY(15%);
366 | }
367 | `;
368 |
369 | this.mount = async () => {
370 | try {
371 | let url = new URL(
372 | import.meta.env.VITE_SIMPLE_DOWNLOAD_FILE,
373 | location.href
374 | ).href;
375 | let file = await realFetch(url);
376 | console.log(file);
377 | if (file.status != 200) {
378 | throw new Error(
379 | `Failed to download file: ${file.status} ${file.statusText}`
380 | );
381 | }
382 | let parsedSize = 0;
383 | const fileSize = parseInt(file.headers.get("Content-Length")!);
384 | const stream = file.body!;
385 | const reader = stream.getReader();
386 | const self = this;
387 | let progressStream = new ReadableStream({
388 | async pull(controller) {
389 | const { value, done } = await reader.read();
390 |
391 | if (!value || done) {
392 | controller.close();
393 | } else {
394 | controller.enqueue(value);
395 |
396 | parsedSize += value.byteLength;
397 | self.percent = (parsedSize / fileSize) * 100;
398 | }
399 | },
400 | });
401 |
402 | this.extracting = true;
403 |
404 | if (url.endsWith(".gz"))
405 | progressStream = progressStream.pipeThrough(
406 | new DecompressionStream("gzip")
407 | );
408 | await extractTar(progressStream, rootFolder, (type, name) => {
409 | console.log(`extracted ${type} ${name}`);
410 | });
411 |
412 | this.extracting = false;
413 | await rootFolder.getFileHandle(".ContentExists", { create: true });
414 | this["on:done"]();
415 | } catch (e) {
416 | this.status = `Failed to download file: ${e}`;
417 | this.extracting = false;
418 | console.error(e);
419 | }
420 | };
421 |
422 | return (
423 |
424 |
Downloading {NAME} from the server
425 | {$if(use(this.extracting),
)}
426 | {$if(use(this.status),
{use(this.status)}
)}
427 |
428 | );
429 | };
430 |
431 | export const Download: Component<
432 | {
433 | "on:done": () => void;
434 | },
435 | {
436 | downloading: boolean;
437 | status: string;
438 | percent: number;
439 | input: HTMLInputElement;
440 |
441 | username: string;
442 | password: string;
443 | }
444 | > = function () {
445 | this.username = "";
446 | this.password = "";
447 |
448 | this.css = `
449 | display: flex;
450 | flex-direction: column;
451 | gap: 1rem;
452 | font-size: 15pt;
453 |
454 | input[type="file"] {
455 | display: none;
456 | }
457 |
458 | .methods {
459 | display: flex;
460 | gap: 1rem;
461 | }
462 | .methods > div {
463 | flex: 1;
464 | display: flex;
465 | flex-direction: column;
466 | gap: 0.5rem;
467 |
468 | padding: 1rem;
469 | }
470 | input {
471 | color: var(--fg);
472 | background: var(--bg);
473 | border: 2px solid black;
474 | border-radius: 0.5em;
475 | padding: 0.25rem;
476 |
477 | font-family: Andy Bold;
478 | font-size: 18pt;
479 | }
480 |
481 | .spacer {
482 | flex: 1;
483 | margin-top: 0.5em;
484 | margin-bottom: 0.5em;
485 | border-bottom: 1px solid var(--fg);
486 | }
487 |
488 | h1, h3 {
489 | text-align: center;
490 | font-family: Andy Bold;
491 | padding: 0;
492 | margin: 0;
493 | }
494 | .logcontainer {
495 | font-size: initial;
496 | }
497 |
498 | .qrcontainer {
499 | display: flex;
500 | justify-content: center;
501 | flex-direction: column;
502 | align-items: center;
503 | width: 100%;
504 | }
505 | .qrcontainer img {
506 | width: 40%;
507 | }
508 | `;
509 |
510 | const loginqr = async () => {
511 | gameState.loginstate = 1;
512 | let result = await initSteam(null, null, true);
513 | if (result != 0) {
514 | gameState.loginstate = 3;
515 | } else {
516 | gameState.loginstate = 2;
517 | }
518 | };
519 |
520 | const loginpass = async () => {
521 | gameState.loginstate = 1;
522 | let result = await initSteam(this.username, this.password, false);
523 | if (result != 0) {
524 | this.username = "";
525 | this.password = "";
526 | gameState.loginstate = 3;
527 | } else {
528 | gameState.loginstate = 2;
529 | }
530 | };
531 | const download = async () => {
532 | this.downloading = true;
533 | await downloadApp();
534 | };
535 |
536 | return (
537 |
538 |
Steam Login
539 |
540 | This will log into Steam through a proxy, so that it can download
541 | Terraria assets and achievement stats
542 | The account details are encrpyted on your device and never sent to a
543 | server. Still, beware of unofficial deployments
544 |
545 |
546 | {$if(
547 | use(gameState.loginstate, (l) => l == 0 || l == 3),
548 |
549 |
550 |
Username and Password
551 |
552 |
557 |
563 |
564 | Log In with Username and Password
565 |
566 |
567 |
568 |
Steam Guard QR Code
569 | Requires the Steam app on your phone to be installed.
570 |
571 |
577 |
578 | Log In with QR Code
579 |
580 |
581 |
582 | )}
583 |
584 | {$if(
585 | use(gameState.loginstate, (l) => l == 3),
586 |
Failed to log in! Try again
587 | )}
588 |
589 | {$if(
590 | use(gameState.loginstate, (l) => l == 3 || l == 1 || l == 2),
591 |
592 |
593 |
594 | )}
595 |
596 | {$if(
597 | use(gameState.loginstate, (l) => l == 1),
598 |
599 |
600 | Since this uses a proxy, the steam app might complain about your
601 | location being wrong. Just select the location that you don't
602 | usually log in from if it asks
603 |
604 | {$if(use(gameState.qr),
)}
605 |
606 | {$if(
607 | use(gameState.qr),
608 |
Scan this QR code with the Steam app on your phone.
609 | )}
610 |
611 | )}
612 |
613 | {$if(
614 | use(gameState.loginstate, (l) => l == 2),
615 |
616 |
622 |
623 | Download Assets
624 |
625 |
626 | )}
627 |
628 | {$if(use(this.downloading),
)}
629 |
630 | );
631 | };
632 |
633 | export const Splash: Component<
634 | {
635 | "on:next": () => void;
636 | },
637 | {
638 | next: "" | "copy" | "download" | "extract" | "simpledownload";
639 | }
640 | > = function () {
641 | this.css = `
642 | position: relative;
643 |
644 | .splash, .blur, .main {
645 | position: absolute;
646 | width: 100%;
647 | height: 100%;
648 | top: 0;
649 | left: 0;
650 | }
651 |
652 | .splash {
653 | object-fit: cover;
654 | z-index: 1;
655 | }
656 |
657 | .blur {
658 | backdrop-filter: blur(0.5vw);
659 | z-index: 2;
660 | }
661 |
662 | .main {
663 | display: flex;
664 | flex-direction: column;
665 | align-items: center;
666 | justify-content: center;
667 | z-index: 3;
668 | padding-bottom: 5em;
669 | }
670 |
671 | .container {
672 | backdrop-filter: blur(0.5vw);
673 | width: min(50rem, 100%);
674 | flex: 1;
675 | margin: 0 1rem;
676 | padding: 1em;
677 | font-size: 18pt;
678 |
679 | color: var(--fg);
680 |
681 | display: flex;
682 | flex-direction: column;
683 | gap: 0.5rem;
684 | }
685 |
686 | .logo {
687 | display: flex;
688 | justify-content: center;
689 | }
690 |
691 | .wisp {
692 | display: flex;
693 | gap: 0.5em;
694 | align-items: center;
695 | }
696 | .wisp input {
697 | flex: 1;
698 | }
699 | `;
700 |
701 | this.next = "";
702 |
703 | return (
704 |
705 |
706 |
707 |
708 |
709 |
710 | {use(this.next, (x) => {
711 | if (!x) {
712 | return
(this.next = x)} />;
713 | } else if (x === "copy") {
714 | return ;
715 | } else if (x === "extract") {
716 | return ;
717 | } else if (x === "simpledownload") {
718 | return ;
719 | } else if (x === "download") {
720 | return ;
721 | }
722 | })}
723 |
724 | Wisp Proxy Server:
725 |
726 |
727 |
728 |
729 |
730 | );
731 | };
732 |
--------------------------------------------------------------------------------
/src/store.ts:
--------------------------------------------------------------------------------
1 | export let store = $store(
2 | {
3 | theme:
4 | window.matchMedia &&
5 | window.matchMedia("(prefers-color-scheme: light)").matches
6 | ? "light"
7 | : "dark",
8 | wisp: "wss://anura.pro/",
9 | },
10 | { ident: "options", backing: "localstorage", autosave: "auto" }
11 | );
12 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | padding: 0;
4 | margin: 0;
5 | width: 100%;
6 | height: 100%;
7 | }
8 |
9 | * {
10 | outline-color: transparent;
11 | box-sizing: border-box;
12 | }
13 |
14 | #app {
15 | width: 100%;
16 | height: 100%;
17 |
18 | --font-body:
19 | "Andy Bold", -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
20 | Roboto, "Helvetica Neue", Arial, sans-serif;
21 | --font-display:
22 | "Inter Tight", "Inter", -apple-system, system-ui, BlinkMacSystemFont,
23 | "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
24 | --font-mono:
25 | "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
26 | "Liberation Mono", "Courier New", monospace;
27 |
28 | font-family: var(--font-body);
29 | }
30 |
31 | .tcontainer {
32 | border: 2px solid black;
33 | background-color: var(--bg);
34 | border-radius: 0.75rem;
35 | }
36 |
37 | #app.dark {
38 | --bg-sub: #131313;
39 | --bg: #0e0e66ab;
40 | --surface0: #1e1e1e;
41 | --surface1: #2b2b2b;
42 | --surface2: #333333;
43 | --surface3: #3b3b3b;
44 | --surface4: #444444;
45 | --surface5: #4c4c4c;
46 | --surface6: #555555;
47 | --accent: #84f084;
48 | --error: #f02424;
49 | --success: #84f084;
50 | --warning: #f0f084;
51 | --info: #7c98f6;
52 | --fg: #f0f0f0;
53 | --fg2: #e0e0e0;
54 | --fg3: #d0d0d0;
55 | --fg4: #c0c0c0;
56 | --fg5: #b0b0b0;
57 | --fg6: #a0a0a0;
58 | }
59 |
60 | #app.light {
61 | --bg-sub: #fafafa;
62 | --bg: #f0f0f0;
63 | --surface0: #e0e0e0;
64 | --surface1: #d0d0d0;
65 | --surface2: #c0c0c0;
66 | --surface3: #b0b0b0;
67 | --surface4: #a0a0a0;
68 | --surface5: #909090;
69 | --surface6: #808080;
70 | --accent: #f06060;
71 | --error: #e43e3e;
72 | --success: #14b614;
73 | --warning: #e0e060;
74 | --info: #6097f0;
75 | --fg: #171717;
76 | --fg2: #2b2b2b;
77 | --fg3: #3b3b3b;
78 | --fg4: #4c4c4c;
79 | --fg5: #555555;
80 | --fg6: #5f5f5f;
81 | }
82 |
83 | @font-face {
84 | font-family: "Andy Bold";
85 | src: local("Andy Bold"), url("/AndyBold.ttf");
86 | }
87 |
88 | h1,
89 | h2,
90 | h3,
91 | h4,
92 | h5,
93 | h6 {
94 | font-family: var(--font-display);
95 | }
96 |
97 | h1 {
98 | font-weight: 680;
99 | }
100 |
101 | h2 {
102 | font-weight: 600;
103 | }
104 |
105 | h3 {
106 | font-weight: 500;
107 | }
108 |
109 | a {
110 | color: var(--accent);
111 | text-decoration: none;
112 | transition: color 0.15s;
113 |
114 | &:hover {
115 | color: color-mix(in srgb, var(--accent) 80%, white);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/ui/Button.tsx:
--------------------------------------------------------------------------------
1 | import type { IconifyIcon } from "@iconify/types";
2 |
3 | export const Icon: Component<{ icon: IconifyIcon; class?: string }, {}> =
4 | function () {
5 | // @ts-expect-error
6 | this._leak = true;
7 | this.mount = () => {
8 | this.root.innerHTML = this.icon.body;
9 | useChange([this.icon], () => {
10 | this.root.innerHTML = this.icon.body;
11 | });
12 | };
13 | return (
14 |
21 | );
22 | };
23 |
24 | export const Button: Component<
25 | {
26 | "on:click": (() => void) | ((e: PointerEvent) => void);
27 |
28 | class?: string;
29 | type: "primary" | "normal" | "listitem" | "listaction";
30 | icon: "full" | "left" | "none";
31 | disabled: boolean;
32 | title?: string;
33 | },
34 | {
35 | children: any;
36 | }
37 | > = function () {
38 | // @ts-expect-error
39 | this._leak = true;
40 | this.css = `
41 | button {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 |
46 | width: 100%;
47 | height: 100%;
48 |
49 | padding: 0.5rem;
50 |
51 | transition: background 0.25s;
52 | font-family: var(--font-body);
53 | cursor: pointer;
54 | font-size: 13pt;
55 | }
56 |
57 | button.icon-full svg, button.icon-left svg {
58 | width: 1.5rem;
59 | height: 1.5rem;
60 | }
61 | button.icon-full {
62 | }
63 | button.icon-left {
64 | gap: 0.25rem;
65 | }
66 |
67 | button.type-primary {
68 | background: var(--bg);
69 | color: var(--fg);
70 | }
71 | button.type-normal {
72 | background: var(--bg);
73 | color: var(--fg);
74 | }
75 | button.type-listitem {
76 | background: transparent;
77 | color: var(--fg);
78 | border-radius: 0.5rem;
79 | }
80 | button.type-listaction {
81 | background: var(--bg);
82 | color: var(--fg);
83 | }
84 |
85 | button.type-primary:not(:disabled):hover {
86 | background: color-mix(in srgb, var(--bg) 95%, white);
87 | }
88 | button.type-primary:not(:disabled):active {
89 | background: color-mix(in srgb, var(--bg) 95%, white);
90 | }
91 | button.type-normal:not(:disabled):hover {
92 | background: var(--surface2);
93 | }
94 | button.type-normal:not(:disabled):active {
95 | background: var(--surface3);
96 | }
97 | button.type-listitem:not(:disabled):hover {
98 | background: var(--surface1);
99 | }
100 | button.type-listitem:not(:disabled):active {
101 | background: var(--surface2);
102 | }
103 | button.type-listaction:not(:disabled):hover {
104 | background: var(--surface3);
105 | }
106 | button.type-listaction:not(:disabled):active {
107 | background: var(--surface4);
108 | }
109 |
110 | button:disabled {
111 | background: var(--surface0);
112 | cursor: not-allowed;
113 | }
114 | `;
115 | return (
116 |
117 |
123 | {use(this.children)}
124 |
125 |
126 | );
127 | };
128 | export const Link: Component<{ href: string }, { children: any[] }> =
129 | function () {
130 | return (
131 |
132 | {this.children}
133 |
134 | );
135 | };
136 |
--------------------------------------------------------------------------------
/src/ui/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import iconClose from "@ktibow/iconset-material-symbols/close";
2 | import { Button, Icon } from "./Button";
3 |
4 | export const Dialog: Component<
5 | { name: string; open: boolean },
6 | { children: any[] }
7 | > = function () {
8 | this.css = `
9 | display: flex;
10 | flex-direction: column;
11 | gap: 0.8rem;
12 |
13 | background: var(--bg);
14 | color: var(--fg);
15 | border: 1.25px solid var(--surface3);
16 | border-radius: 1.5rem;
17 |
18 | width: min(40rem, 100%);
19 | min-height: min(50rem, 100%);
20 | max-height: min(50rem, 100%);
21 |
22 | position: fixed;
23 | inset: 0;
24 | opacity: 0;
25 |
26 | scale: .9;
27 | transform: rotate3d(1, 0, 0, -20deg);
28 | filter: brightness(1.5);
29 |
30 | pointer-events: none;
31 | transition: opacity 0.25s, transform 0.175s, filter 0.2s, scale 0.2s, background 0.1s, border-color 0.1s;
32 | transition-timing-function: ease;
33 | transition-delay: 0.05s, 0.05s, 0.05s, 0.05s;
34 | transform-origin: 50% 0%;
35 | perspective: 1250px;
36 |
37 | &[open] {
38 | opacity: 1;
39 | transform: rotate3d(1,0,0,0deg);
40 | filter: brightness(1.0);
41 | transition-delay: 0.05s, 0.05s, 0.05s, 0.2s;
42 | pointer-events: auto;
43 | }
44 |
45 | &[open]::backdrop {
46 | background: rgba(32, 28, 28, 0.35);
47 | }
48 |
49 | &::backdrop {
50 | background: rgba(32, 28, 28, 0);
51 | transition: background 0.2s;
52 | }
53 |
54 | .header {
55 | display: flex;
56 | gap: 0.5rem;
57 | align-items: center;
58 | border-bottom: 1.8px solid var(--surface2);
59 | transition: border-color 0.1s ease;
60 | padding-bottom: 0.5rem;
61 | user-select: none;
62 | -webkit-user-select: none;
63 | }
64 |
65 | .header h2 {
66 | margin: 0;
67 | }
68 |
69 | .children {
70 | overflow-y: scroll;
71 | overflow-x: hidden;
72 | scrollbar-width: none;
73 | scrollbar-color: transparent transparent;
74 | }
75 |
76 | .expand { flex: 1 }
77 | `;
78 | this.mount = () => {
79 | const root = this.root as HTMLDialogElement;
80 | useChange([this.open], () => {
81 | if (this.open) {
82 | root.showModal();
83 | } else {
84 | root.close();
85 | }
86 | });
87 | };
88 | return (
89 |
90 |
105 | {this.children}
106 |
107 | );
108 | };
109 |
--------------------------------------------------------------------------------
/src/ui/Switch.tsx:
--------------------------------------------------------------------------------
1 | export const Switch: Component<
2 | {
3 | "on:change"?: (() => void) | ((e: InputEvent) => void);
4 | on: boolean;
5 | title: string;
6 | disabled: boolean;
7 | class?: string;
8 | },
9 | {}
10 | > = function () {
11 | // @ts-expect-error
12 | this._leak = true;
13 | const transition = "background 0.2s, transform 0.2s, width 0.2s";
14 |
15 | this.css = `
16 | align-items: center;
17 | display: flex;
18 | justify-content: space-between;
19 |
20 | user-select: none;
21 | -webkit-user-select: none;
22 |
23 | .switch-container {
24 | position: relative;
25 | display: inline-block;
26 | width: 3.2rem;
27 | height: 1.8rem;
28 | flex: 0 0 auto;
29 | max-width: 3.2rem;
30 | }
31 |
32 | .switch-container input {
33 | opacity: 0;
34 | width: 0;
35 | height: 0;
36 | margin: 0;
37 | }
38 |
39 | .switch-slider {
40 | position: absolute;
41 | cursor: pointer;
42 | top: 0;
43 | left: 0;
44 | right: 0;
45 | bottom: 0;
46 | background-color: var(--surface3);
47 | transition: ${transition};
48 | border-radius: 2rem;
49 | box-shadow: inset 0px 0px 5px -0.1px color-mix(in srgb, var(--fg) 10%, transparent), inset 0px -0.7px 1px 0px color-mix(in srgb, var(--fg) 17.5%, transparent);
50 | }
51 |
52 | .switch-slider::before {
53 | position: absolute;
54 | content: "";
55 | height: 1.4rem;
56 | width: 1.4rem;
57 | left: 0.2rem;
58 | bottom: 0.2rem;
59 | background-color: white;
60 | transition: ${transition};
61 | border-radius: 1.5rem;
62 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
63 | }
64 |
65 | input:checked + .switch-slider {
66 | background-color: var(--accent);
67 | }
68 |
69 | input:active + .switch-slider {
70 | background-color: var(--surface5);
71 | }
72 |
73 | .switch-container:hover input:checked:not(:disabled) + .switch-slider {
74 | background-color: color-mix(in srgb, var(--accent) 87.5%, var(--fg));
75 | }
76 |
77 | input:checked:active + .switch-slider {
78 | background-color: color-mix(in srgb, var(--accent) 70%, var(--fg));
79 | }
80 |
81 | .switch-container:hover input:not(:checked):not(:disabled) + .switch-slider {
82 | background-color: var(--surface4);
83 | }
84 |
85 | input:not(:disabled):active + .switch-slider::before {
86 | width: 1.7rem;
87 | transition: ${transition}
88 | }
89 |
90 | input:not(:disabled):checked:active + .switch-slider::before {
91 | transform: translateX(1.1rem);
92 | }
93 |
94 | input:disabled + .switch-slider {
95 | background-color: var(--surface0);
96 | cursor: not-allowed;
97 | }
98 |
99 | input:checked:disabled + .switch-slider {
100 | background-color: color-mix(in srgb, var(--accent) 50%, var(--surface3));
101 | }
102 |
103 | input:checked + .switch-slider::before {
104 | transform: translateX(1.4rem);
105 | }
106 |
107 | input:disabled + .switch-slider::before {
108 | background-color: color-mix(in srgb, #888888 92.5%, var(--accent));
109 | }
110 |
111 | input:checked:disabled + .switch-slider::before {
112 | background-color: color-mix(in srgb, #bbbbbb 92.5%, var(--accent));
113 | }
114 | `;
115 |
116 | return (
117 |
118 | {use(this.title)}
119 |
120 | {})}
125 | />
126 |
127 |
128 |
129 | );
130 | };
131 |
--------------------------------------------------------------------------------
/src/ui/TextField.tsx:
--------------------------------------------------------------------------------
1 | export const TextField: Component<
2 | {
3 | "on:keydown"?: (() => void) | ((e: KeyboardEvent) => void);
4 | value: string;
5 | placeholder?: string;
6 | class?: string;
7 | type?: string;
8 | },
9 | {}
10 | > = function () {
11 | this.css = `
12 | border: 0.1rem solid var(--surface1);
13 | border-radius: 4rem;
14 | padding: 0.5rem;
15 | font-family: var(--font-body);
16 | padding-left: 0.75rem;
17 | transition: all 0.1s ease;
18 | color: var(--fg);
19 | font-size: 1.25rem;
20 |
21 | &:hover {
22 | transition: all 0.1s ease;
23 | border-color: var(--surface2);
24 | }
25 |
26 | &:focus {
27 | transition: all 0.1s ease;
28 | border-color: var(--accent);
29 | }
30 |
31 | ::placeholder {
32 | color: var(--surface5);
33 | }
34 | `;
35 |
36 | return (
37 | {})}
43 | />
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | interface ImportMetaEnv {
4 | readonly VITE_SIMPLE_DOWNLOAD: string;
5 | readonly VITE_SIMPLE_DOWNLOAD_FILE: string;
6 | }
7 |
8 | interface ImportMeta {
9 | readonly env: ImportMetaEnv;
10 | }
11 |
--------------------------------------------------------------------------------
/terraria/CloudSocialModule.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Steamworks;
4 | using Terraria.Social.Base;
5 | using System.IO;
6 |
7 |
8 | namespace Terraria.Social.Custom;
9 |
10 | public class CloudSocialModule : Terraria.Social.Base.CloudSocialModule
11 | {
12 | private const uint WRITE_CHUNK_SIZE = 1024u;
13 |
14 | private object ioLock = new object();
15 |
16 | private byte[] writeBuffer = new byte[1024];
17 |
18 |
19 | string prefix = "/libsdl/remote/";
20 |
21 | public override void Initialize()
22 | {
23 | }
24 |
25 | public override void Shutdown()
26 | {
27 | }
28 |
29 | public override IEnumerable GetFiles()
30 | {
31 | lock (ioLock)
32 | {
33 | var files = Directory.EnumerateFiles(prefix, "*", SearchOption.AllDirectories);
34 | List list = new List();
35 | foreach (var file in files)
36 | {
37 | list.Add(file.Substring(prefix.Length));
38 | }
39 | return list;
40 | }
41 | }
42 |
43 | public override bool Write(string path, byte[] data, int length)
44 | {
45 | lock (ioLock)
46 | {
47 | var fs = File.Open(prefix + path, FileMode.Create);
48 | fs.Write(data, 0, length);
49 | fs.Close();
50 |
51 | Console.WriteLine("TODO: Implement CloudSocialModule.Write");
52 | return true;
53 | }
54 | }
55 |
56 | public override int GetFileSize(string path)
57 | {
58 | lock (ioLock)
59 | {
60 | return File.ReadAllBytes(prefix + path).Length;
61 | }
62 | }
63 |
64 | public override void Read(string path, byte[] buffer, int size)
65 | {
66 | lock (ioLock)
67 | {
68 | File.ReadAllBytes(prefix + path).CopyTo(buffer, 0);
69 | }
70 | }
71 |
72 | public override bool HasFile(string path)
73 | {
74 | lock (ioLock)
75 | {
76 | return File.Exists(prefix + path);
77 | }
78 | }
79 |
80 | public override bool Delete(string path)
81 | {
82 | lock (ioLock)
83 | {
84 | Console.WriteLine("TODO: Implement CloudSocialModule.Delete");
85 | return true;
86 | }
87 | }
88 |
89 | public override bool Forget(string path)
90 | {
91 | lock (ioLock)
92 | {
93 | Console.WriteLine("TODO: Implement CloudSocialModule.Forget");
94 | return true;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/terraria/Emscripten.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | int mount_opfs() {
8 | emscripten_console_log("mount_opfs: starting");
9 | backend_t opfs = wasmfs_create_opfs_backend();
10 | emscripten_console_log("mount_opfs: created opfs backend");
11 | int ret = wasmfs_create_directory("/libsdl", 0777, opfs);
12 | emscripten_console_log("mount_opfs: mounted opfs");
13 | return ret;
14 | }
15 |
16 | void *SDL_CreateWindow(char *title, int w, int h, uint64_t flags);
17 | void *SDL__CreateWindow(char *title, int w, int h, unsigned int flags) {
18 | return SDL_CreateWindow(title, w, h, (uint64_t)flags);
19 | }
20 | uint64_t SDL_GetWindowFlags(void *window);
21 | uint32_t SDL__GetWindowFlags(void *window) {
22 | return (uint32_t)SDL_GetWindowFlags(window);
23 | }
24 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/ReLogic/Localization/IME/FnaIme.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/ReLogic/Localization/IME/FnaIme.cs
2 | +++ terraria/ReLogic/Localization/IME/FnaIme.cs
3 | @@ -1,4 +1,5 @@
4 | using Microsoft.Xna.Framework.Input;
5 | +using System;
6 |
7 | namespace ReLogic.Localization.IME;
8 |
9 | @@ -14,9 +15,10 @@
10 |
11 | public override uint SelectedCandidate => 0u;
12 |
13 | - public FnaIme()
14 | + public FnaIme(IntPtr windowHandle)
15 | {
16 | TextInputEXT.TextInput += OnCharCallback;
17 | + TextInputEXT.WindowHandle = windowHandle;
18 | }
19 |
20 | private void OnCharCallback(char key)
21 | @@ -48,6 +50,16 @@
22 | }
23 | }
24 |
25 | + protected override void OnEnable() {
26 | + base.OnEnable();
27 | + TextInputEXT.StartTextInput();
28 | + }
29 | + protected override void OnDisable()
30 | + {
31 | + base.OnDisable();
32 | + TextInputEXT.StopTextInput();
33 | + }
34 | +
35 | ~FnaIme()
36 | {
37 | Dispose(disposing: false);
38 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/ReLogic/OS/Linux/LinuxPlatform.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/ReLogic/OS/Linux/LinuxPlatform.cs
2 | +++ terraria/ReLogic/OS/Linux/LinuxPlatform.cs
3 | @@ -16,6 +16,6 @@
4 |
5 | public override void InitializeClientServices(IntPtr windowHandle)
6 | {
7 | - RegisterService((IImeService)new FnaIme());
8 | + RegisterService((IImeService)new FnaIme(windowHandle));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/ReLogic/OS/OSX/OsxPlatform.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/ReLogic/OS/OSX/OsxPlatform.cs
2 | +++ terraria/ReLogic/OS/OSX/OsxPlatform.cs
3 | @@ -16,6 +16,6 @@
4 |
5 | public override void InitializeClientServices(IntPtr windowHandle)
6 | {
7 | - RegisterService((IImeService)new FnaIme());
8 | + RegisterService((IImeService)new FnaIme(windowHandle));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Achievements/AchievementManager.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Achievements/AchievementManager.cs
2 | +++ terraria/Terraria/Achievements/AchievementManager.cs
3 | @@ -74,11 +74,9 @@
4 | try
5 | {
6 | using MemoryStream memoryStream = new MemoryStream();
7 | - using CryptoStream cryptoStream = new CryptoStream(memoryStream, new RijndaelManaged().CreateEncryptor(_cryptoKey, _cryptoKey), CryptoStreamMode.Write);
8 | - using BsonWriter bsonWriter = new BsonWriter(cryptoStream);
9 | + using BsonWriter bsonWriter = new BsonWriter(memoryStream);
10 | JsonSerializer.Create(_serializerSettings).Serialize(bsonWriter, _achievements);
11 | bsonWriter.Flush();
12 | - cryptoStream.FlushFinalBlock();
13 | FileUtilities.WriteAllBytes(path, memoryStream.ToArray(), cloud);
14 | }
15 | catch (Exception exception)
16 | @@ -112,8 +110,7 @@
17 | try
18 | {
19 | using MemoryStream stream = new MemoryStream(buffer);
20 | - using CryptoStream stream2 = new CryptoStream(stream, new RijndaelManaged().CreateDecryptor(_cryptoKey, _cryptoKey), CryptoStreamMode.Read);
21 | - using BsonReader reader = new BsonReader(stream2);
22 | + using BsonReader reader = new BsonReader(stream);
23 | dictionary = JsonSerializer.Create(_serializerSettings).Deserialize>(reader);
24 | }
25 | catch (Exception)
26 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Audio/MP3AudioTrack.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Audio/MP3AudioTrack.cs
2 | +++ terraria/Terraria/Audio/MP3AudioTrack.cs
3 | @@ -1,6 +1,6 @@
4 | using System.IO;
5 | using Microsoft.Xna.Framework.Audio;
6 | -using XPT.Core.Audio.MP3Sharp;
7 | +using MP3Sharp;
8 |
9 | namespace Terraria.Audio;
10 |
11 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Audio/OGGAudioTrack.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Audio/OGGAudioTrack.cs
2 | +++ terraria/Terraria/Audio/OGGAudioTrack.cs
3 | @@ -59,7 +59,7 @@
4 |
5 | private void FindLoops()
6 | {
7 | - IDictionary> all = _vorbisReader.Tags.All;
8 | + IDictionary> all = (IDictionary>)_vorbisReader.Tags.All;
9 | TryReadingTag(all, "LOOPSTART", ref _loopStart);
10 | TryReadingTag(all, "LOOPEND", ref _loopEnd);
11 | }
12 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Audio/SoundEngine.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Audio/SoundEngine.cs
2 | +++ terraria/Terraria/Audio/SoundEngine.cs
3 | @@ -18,7 +18,7 @@
4 |
5 | public static void Initialize()
6 | {
7 | - IsAudioSupported = TestAudioSupport();
8 | + IsAudioSupported = true; // TestAudioSupport();
9 | }
10 |
11 | public static void Load(IServiceProvider services)
12 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/GameInput/PlayerInput.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/GameInput/PlayerInput.cs
2 | +++ terraria/Terraria/GameInput/PlayerInput.cs
3 | @@ -1231,7 +1231,6 @@
4 | {
5 | flag2 = true;
6 | }
7 | - Main.ChromaPainter.PressKey(pressedKeys[i]);
8 | }
9 | if (Main.blockKey != Keys.None.ToString())
10 | {
11 | @@ -1413,7 +1412,6 @@
12 | ListenFor(null, InputMode.Keyboard);
13 | Main.blockKey = newKey;
14 | Main.blockInput = false;
15 | - Main.ChromaPainter.CollectBoundKeys();
16 | }
17 | if (CurrentlyRebinding && _listeningInputMode == InputMode.KeyboardUI)
18 | {
19 | @@ -1431,7 +1429,6 @@
20 | ListenFor(null, InputMode.KeyboardUI);
21 | Main.blockKey = newKey;
22 | Main.blockInput = false;
23 | - Main.ChromaPainter.CollectBoundKeys();
24 | }
25 | FixDerpedRebinds();
26 | if (PlayerInput.OnBindingChange != null)
27 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Graphics/Capture/CaptureCamera.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Graphics/Capture/CaptureCamera.cs
2 | +++ terraria/Terraria/Graphics/Capture/CaptureCamera.cs
3 | @@ -1,13 +1,10 @@
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Drawing;
7 | -using System.Drawing.Imaging;
8 | using System.IO;
9 | -using System.Runtime.InteropServices;
10 | using System.Threading;
11 | using Microsoft.Xna.Framework;
12 | using Microsoft.Xna.Framework.Graphics;
13 | -using ReLogic.OS;
14 | using Terraria.Graphics.Effects;
15 | using Terraria.Localization;
16 | using Terraria.Utilities;
17 | @@ -195,7 +192,7 @@
18 | else
19 | {
20 | _graphics.SetRenderTarget(null);
21 | - SaveImage(_frameBuffer, captureChunk.ScaledArea.Width, captureChunk.ScaledArea.Height, ImageFormat.Png, _activeSettings.OutputName, captureChunk.Area.X + "-" + captureChunk.Area.Y + ".png");
22 | + SaveImage(_frameBuffer, captureChunk.ScaledArea.Width, captureChunk.ScaledArea.Height, _activeSettings.OutputName, captureChunk.Area.X + "-" + captureChunk.Area.Y + ".png");
23 | }
24 | _tilesProcessed += captureChunk.Area.Width * captureChunk.Area.Height;
25 | }
26 | @@ -247,7 +244,7 @@
27 | return _tilesProcessed / _totalTiles;
28 | }
29 |
30 | - private bool SaveImage(int width, int height, ImageFormat imageFormat, string filename)
31 | + private bool SaveImage(int width, int height, string filename)
32 | {
33 | string savePath = Main.SavePath;
34 | char directorySeparatorChar = Path.DirectorySeparatorChar;
35 | @@ -259,22 +256,8 @@
36 | }
37 | try
38 | {
39 | - if (!Platform.IsWindows)
40 | - {
41 | - using FileStream stream = File.Create(filename);
42 | - PlatformUtilities.SavePng(stream, width, height, width, height, _outputData);
43 | - }
44 | - else
45 | - {
46 | - using Bitmap bitmap = new Bitmap(width, height);
47 | - System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, width, height);
48 | - BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
49 | - IntPtr scan = bitmapData.Scan0;
50 | - Marshal.Copy(_outputData, 0, scan, width * height * 4);
51 | - bitmap.UnlockBits(bitmapData);
52 | - bitmap.Save(filename, imageFormat);
53 | - bitmap.Dispose();
54 | - }
55 | + using FileStream stream = File.Create(filename);
56 | + PlatformUtilities.SavePng(stream, width, height, width, height, _outputData);
57 | return true;
58 | }
59 | catch (Exception value)
60 | @@ -284,7 +267,7 @@
61 | }
62 | }
63 |
64 | - private void SaveImage(Texture2D texture, int width, int height, ImageFormat imageFormat, string foldername, string filename)
65 | + private void SaveImage(Texture2D texture, int width, int height, string foldername, string filename)
66 | {
67 | string[] obj = new string[5]
68 | {
69 | @@ -306,54 +289,26 @@
70 | {
71 | return;
72 | }
73 | - if (!Platform.IsWindows)
74 | - {
75 | - int elementCount = texture.Width * texture.Height * 4;
76 | - texture.GetData(_outputData, 0, elementCount);
77 | - int num = 0;
78 | - int num2 = 0;
79 | - for (int i = 0; i < height; i++)
80 | - {
81 | - for (int j = 0; j < width; j++)
82 | - {
83 | - _outputData[num2] = _outputData[num];
84 | - _outputData[num2 + 1] = _outputData[num + 1];
85 | - _outputData[num2 + 2] = _outputData[num + 2];
86 | - _outputData[num2 + 3] = _outputData[num + 3];
87 | - num += 4;
88 | - num2 += 4;
89 | - }
90 | - num += texture.Width - width << 2;
91 | - }
92 | - using FileStream stream = File.Create(text2);
93 | - PlatformUtilities.SavePng(stream, width, height, width, height, _outputData);
94 | - return;
95 | - }
96 | - using Bitmap bitmap = new Bitmap(width, height);
97 | - System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, width, height);
98 | - int elementCount2 = texture.Width * texture.Height * 4;
99 | - texture.GetData(_outputData, 0, elementCount2);
100 | - int num3 = 0;
101 | - int num4 = 0;
102 | - for (int k = 0; k < height; k++)
103 | - {
104 | - for (int l = 0; l < width; l++)
105 | - {
106 | - byte b = _outputData[num3 + 2];
107 | - _outputData[num4 + 2] = _outputData[num3];
108 | - _outputData[num4] = b;
109 | - _outputData[num4 + 1] = _outputData[num3 + 1];
110 | - _outputData[num4 + 3] = _outputData[num3 + 3];
111 | - num3 += 4;
112 | - num4 += 4;
113 | +
114 | + int elementCount = texture.Width * texture.Height * 4;
115 | + texture.GetData(_outputData, 0, elementCount);
116 | + int num = 0;
117 | + int num2 = 0;
118 | + for (int i = 0; i < height; i++)
119 | + {
120 | + for (int j = 0; j < width; j++)
121 | + {
122 | + _outputData[num2] = _outputData[num];
123 | + _outputData[num2 + 1] = _outputData[num + 1];
124 | + _outputData[num2 + 2] = _outputData[num + 2];
125 | + _outputData[num2 + 3] = _outputData[num + 3];
126 | + num += 4;
127 | + num2 += 4;
128 | }
129 | - num3 += texture.Width - width << 2;
130 | + num += texture.Width - width << 2;
131 | }
132 | - BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
133 | - IntPtr scan = bitmapData.Scan0;
134 | - Marshal.Copy(_outputData, 0, scan, width * height * 4);
135 | - bitmap.UnlockBits(bitmapData);
136 | - bitmap.Save(text2, imageFormat);
137 | + using FileStream stream = File.Create(text2);
138 | + PlatformUtilities.SavePng(stream, width, height, width, height, _outputData);
139 | }
140 |
141 | private void FinishCapture()
142 | @@ -365,7 +320,6 @@
143 | {
144 | int width = _outputImageSize.Width;
145 | int height = _outputImageSize.Height;
146 | - ImageFormat png = ImageFormat.Png;
147 | string[] obj = new string[6]
148 | {
149 | Main.SavePath,
150 | @@ -382,7 +336,7 @@
151 | obj[3] = directorySeparatorChar.ToString();
152 | obj[4] = _activeSettings.OutputName;
153 | obj[5] = ".png";
154 | - if (SaveImage(width, height, png, string.Concat(obj)))
155 | + if (SaveImage(width, height, string.Concat(obj)))
156 | {
157 | break;
158 | }
159 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Graphics/WindowStateController.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Graphics/WindowStateController.cs
2 | +++ terraria/Terraria/Graphics/WindowStateController.cs
3 | @@ -1,5 +1,3 @@
4 | -using System.Drawing;
5 | -using System.Windows.Forms;
6 | using ReLogic.OS;
7 |
8 | namespace Terraria.Graphics;
9 | @@ -22,16 +20,10 @@
10 |
11 | public void TryMovingToScreen(string screenDeviceName)
12 | {
13 | - if (CanMoveWindowAcrossScreens && TryGetBounds(screenDeviceName, out var bounds) && IsVisibleOnAnyScreen(bounds))
14 | - {
15 | - Form form = Control.FromHandle(Main.instance.Window.Handle);
16 | - if (WouldViewFitInScreen(form.Bounds, bounds))
17 | - {
18 | - form.Location = new Point(bounds.Width / 2 - form.Width / 2 + bounds.X, bounds.Height / 2 - form.Height / 2 + bounds.Y);
19 | - }
20 | - }
21 | +
22 | }
23 |
24 | + /*
25 | private bool TryGetBounds(string screenDeviceName, out Rectangle bounds)
26 | {
27 | bounds = default(Rectangle);
28 | @@ -68,4 +60,5 @@
29 | }
30 | return false;
31 | }
32 | + */
33 | }
34 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Localization/LanguageManager.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Localization/LanguageManager.cs
2 | +++ terraria/Terraria/Localization/LanguageManager.cs
3 | @@ -18,427 +18,428 @@
4 |
5 | public class LanguageManager
6 | {
7 | - public static LanguageManager Instance = new LanguageManager();
8 | + public static LanguageManager Instance = new LanguageManager();
9 |
10 | - private readonly Dictionary _localizedTexts = new Dictionary();
11 | + private readonly Dictionary _localizedTexts = new Dictionary();
12 |
13 | - private readonly Dictionary> _categoryGroupedKeys = new Dictionary>();
14 | + private readonly Dictionary> _categoryGroupedKeys = new Dictionary>();
15 |
16 | - private GameCulture _fallbackCulture = GameCulture.DefaultCulture;
17 | + private GameCulture _fallbackCulture = GameCulture.DefaultCulture;
18 |
19 | - public GameCulture ActiveCulture { get; private set; }
20 | -
21 | - public event LanguageChangeCallback OnLanguageChanging;
22 | -
23 | - public event LanguageChangeCallback OnLanguageChanged;
24 | -
25 | - private LanguageManager()
26 | - {
27 | - _localizedTexts[""] = LocalizedText.Empty;
28 | - }
29 | -
30 | - public int GetCategorySize(string name)
31 | - {
32 | - if (_categoryGroupedKeys.ContainsKey(name))
33 | - {
34 | - return _categoryGroupedKeys[name].Count;
35 | - }
36 | - return 0;
37 | - }
38 | -
39 | - public void SetLanguage(int legacyId)
40 | - {
41 | - GameCulture language = GameCulture.FromLegacyId(legacyId);
42 | - SetLanguage(language);
43 | - }
44 | -
45 | - public void SetLanguage(string cultureName)
46 | - {
47 | - GameCulture language = GameCulture.FromName(cultureName);
48 | - SetLanguage(language);
49 | - }
50 | -
51 | - public int EstimateWordCount()
52 | - {
53 | - int num = 0;
54 | - foreach (string key in _localizedTexts.Keys)
55 | - {
56 | - string textValue = GetTextValue(key);
57 | - textValue.Replace(",", "").Replace(".", "").Replace("\"", "")
58 | - .Trim();
59 | - string[] array = textValue.Split(new char[1] { ' ' });
60 | - string[] array2 = textValue.Split(new char[1] { ' ' });
61 | - if (array.Length != array2.Length)
62 | - {
63 | - break;
64 | - }
65 | - string[] array3 = array;
66 | - foreach (string text in array3)
67 | - {
68 | - if (!string.IsNullOrWhiteSpace(text) && text.Length >= 1)
69 | - {
70 | - num++;
71 | - }
72 | - }
73 | - }
74 | - return num;
75 | - }
76 | -
77 | - private void SetAllTextValuesToKeys()
78 | - {
79 | - foreach (KeyValuePair localizedText in _localizedTexts)
80 | - {
81 | - localizedText.Value.SetValue(localizedText.Key);
82 | - }
83 | - }
84 | -
85 | - private string[] GetLanguageFilesForCulture(GameCulture culture)
86 | - {
87 | - Assembly.GetExecutingAssembly();
88 | - return Array.FindAll(typeof(Program).Assembly.GetManifestResourceNames(), (string element) => element.StartsWith("Terraria.Localization.Content." + culture.CultureInfo.Name) && element.EndsWith(".json"));
89 | - }
90 | -
91 | - public void SetLanguage(GameCulture culture)
92 | - {
93 | - if (ActiveCulture != culture)
94 | - {
95 | - if (culture != _fallbackCulture && ActiveCulture != _fallbackCulture)
96 | - {
97 | - SetAllTextValuesToKeys();
98 | - LoadLanguage(_fallbackCulture);
99 | - }
100 | - LoadLanguage(culture);
101 | - ActiveCulture = culture;
102 | - Thread.CurrentThread.CurrentCulture = culture.CultureInfo;
103 | - Thread.CurrentThread.CurrentUICulture = culture.CultureInfo;
104 | - if (this.OnLanguageChanged != null)
105 | - {
106 | - this.OnLanguageChanged(this);
107 | - }
108 | - _ = FontAssets.DeathText;
109 | - }
110 | - }
111 | -
112 | - private void LoadLanguage(GameCulture culture, bool processCopyCommands = true)
113 | - {
114 | - LoadFilesForCulture(culture);
115 | - if (this.OnLanguageChanging != null)
116 | - {
117 | - this.OnLanguageChanging(this);
118 | - }
119 | - if (processCopyCommands)
120 | - {
121 | - ProcessCopyCommandsInTexts();
122 | - }
123 | - ChatInitializer.PrepareAliases();
124 | - }
125 | -
126 | - private void LoadFilesForCulture(GameCulture culture)
127 | - {
128 | - string[] languageFilesForCulture = GetLanguageFilesForCulture(culture);
129 | - foreach (string text in languageFilesForCulture)
130 | - {
131 | - try
132 | - {
133 | - string text2 = Utils.ReadEmbeddedResource(text);
134 | - if (text2 == null || text2.Length < 2)
135 | - {
136 | - throw new FormatException();
137 | - }
138 | - LoadLanguageFromFileTextJson(text2, canCreateCategories: true);
139 | - }
140 | - catch (Exception)
141 | - {
142 | - if (Debugger.IsAttached)
143 | - {
144 | - Debugger.Break();
145 | - }
146 | - Console.WriteLine("Failed to load language file: " + text);
147 | - break;
148 | - }
149 | - }
150 | - }
151 | -
152 | - private void ProcessCopyCommandsInTexts()
153 | - {
154 | - Regex regex = new Regex("{\\$(\\w+\\.\\w+)}", RegexOptions.Compiled);
155 | - foreach (KeyValuePair localizedText in _localizedTexts)
156 | - {
157 | - LocalizedText value = localizedText.Value;
158 | - for (int i = 0; i < 100; i++)
159 | - {
160 | - string text = regex.Replace(value.Value, (Match match) => GetTextValue(match.Groups[1].ToString()));
161 | - if (text == value.Value)
162 | - {
163 | - break;
164 | - }
165 | - value.SetValue(text);
166 | - }
167 | - }
168 | - }
169 | -
170 | - public void UseSources(List sourcesFromLowestToHighest)
171 | - {
172 | - string name = ActiveCulture.Name;
173 | - char directorySeparatorChar = Path.DirectorySeparatorChar;
174 | - string assetNameStart = ("Localization" + directorySeparatorChar + name).ToLower();
175 | - LoadLanguage(ActiveCulture, processCopyCommands: false);
176 | - foreach (IContentSource item in sourcesFromLowestToHighest)
177 | - {
178 | - foreach (string item2 in item.GetAllAssetsStartingWith(assetNameStart))
179 | - {
180 | - string extension = item.GetExtension(item2);
181 | - if (!(extension == ".json") && !(extension == ".csv"))
182 | - {
183 | - continue;
184 | - }
185 | - using Stream stream = item.OpenStream(item2);
186 | - using StreamReader streamReader = new StreamReader(stream);
187 | - string fileText = streamReader.ReadToEnd();
188 | - if (extension == ".json")
189 | - {
190 | - LoadLanguageFromFileTextJson(fileText, canCreateCategories: false);
191 | - }
192 | - if (extension == ".csv")
193 | - {
194 | - LoadLanguageFromFileTextCsv(fileText);
195 | - }
196 | - }
197 | - }
198 | - ProcessCopyCommandsInTexts();
199 | - ChatInitializer.PrepareAliases();
200 | - }
201 | -
202 | - public void LoadLanguageFromFileTextCsv(string fileText)
203 | - {
204 | - using TextReader reader = new StringReader(fileText);
205 | - using CsvReader csvReader = new CsvReader(reader);
206 | - csvReader.Configuration.HasHeaderRecord = true;
207 | - if (!csvReader.ReadHeader())
208 | - {
209 | - return;
210 | - }
211 | - string[] fieldHeaders = csvReader.FieldHeaders;
212 | - int num = -1;
213 | - int num2 = -1;
214 | - for (int i = 0; i < fieldHeaders.Length; i++)
215 | - {
216 | - string text = fieldHeaders[i].ToLower();
217 | - if (text == "translation")
218 | - {
219 | - num2 = i;
220 | - }
221 | - if (text == "key")
222 | - {
223 | - num = i;
224 | - }
225 | - }
226 | - if (num == -1 || num2 == -1)
227 | - {
228 | - return;
229 | - }
230 | - int num3 = Math.Max(num, num2) + 1;
231 | - while (csvReader.Read())
232 | - {
233 | - string[] currentRecord = csvReader.CurrentRecord;
234 | - if (currentRecord.Length >= num3)
235 | - {
236 | - string text2 = currentRecord[num];
237 | - string value = currentRecord[num2];
238 | - if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(value) && _localizedTexts.ContainsKey(text2))
239 | - {
240 | - _localizedTexts[text2].SetValue(value);
241 | - }
242 | - }
243 | - }
244 | - }
245 | -
246 | - public void LoadLanguageFromFileTextJson(string fileText, bool canCreateCategories)
247 | - {
248 | - foreach (KeyValuePair> item in JsonConvert.DeserializeObject>>(fileText))
249 | - {
250 | - _ = item.Key;
251 | - foreach (KeyValuePair item2 in item.Value)
252 | - {
253 | - string key = item.Key + "." + item2.Key;
254 | - if (_localizedTexts.ContainsKey(key))
255 | - {
256 | - _localizedTexts[key].SetValue(item2.Value);
257 | - }
258 | - else if (canCreateCategories)
259 | - {
260 | - _localizedTexts.Add(key, new LocalizedText(key, item2.Value));
261 | - if (!_categoryGroupedKeys.ContainsKey(item.Key))
262 | - {
263 | - _categoryGroupedKeys.Add(item.Key, new List());
264 | - }
265 | - _categoryGroupedKeys[item.Key].Add(item2.Key);
266 | - }
267 | - }
268 | - }
269 | - }
270 | -
271 | - [Conditional("DEBUG")]
272 | - private void ValidateAllCharactersContainedInFont(DynamicSpriteFont font)
273 | - {
274 | - if (font == null)
275 | - {
276 | - return;
277 | - }
278 | - string text = "";
279 | - foreach (LocalizedText value2 in _localizedTexts.Values)
280 | - {
281 | - string value = value2.Value;
282 | - for (int i = 0; i < value.Length; i++)
283 | - {
284 | - char c = value[i];
285 | - if (!font.IsCharacterSupported(c))
286 | - {
287 | - text = text + value2.Key + ", " + c.ToString() + ", " + (int)c + "\n";
288 | - }
289 | - }
290 | - }
291 | - }
292 | -
293 | - public LocalizedText[] FindAll(Regex regex)
294 | - {
295 | - int num = 0;
296 | - foreach (KeyValuePair localizedText in _localizedTexts)
297 | - {
298 | - if (regex.IsMatch(localizedText.Key))
299 | - {
300 | - num++;
301 | - }
302 | - }
303 | - LocalizedText[] array = new LocalizedText[num];
304 | - int num2 = 0;
305 | - foreach (KeyValuePair localizedText2 in _localizedTexts)
306 | - {
307 | - if (regex.IsMatch(localizedText2.Key))
308 | - {
309 | - array[num2] = localizedText2.Value;
310 | - num2++;
311 | - }
312 | - }
313 | - return array;
314 | - }
315 | -
316 | - public LocalizedText[] FindAll(LanguageSearchFilter filter)
317 | - {
318 | - LinkedList linkedList = new LinkedList();
319 | - foreach (KeyValuePair localizedText in _localizedTexts)
320 | - {
321 | - if (filter(localizedText.Key, localizedText.Value))
322 | - {
323 | - linkedList.AddLast(localizedText.Value);
324 | - }
325 | - }
326 | - return linkedList.ToArray();
327 | - }
328 | -
329 | - public LocalizedText SelectRandom(LanguageSearchFilter filter, UnifiedRandom random = null)
330 | - {
331 | - int num = 0;
332 | - foreach (KeyValuePair localizedText in _localizedTexts)
333 | - {
334 | - if (filter(localizedText.Key, localizedText.Value))
335 | - {
336 | - num++;
337 | - }
338 | - }
339 | - int num2 = (random ?? Main.rand).Next(num);
340 | - foreach (KeyValuePair localizedText2 in _localizedTexts)
341 | - {
342 | - if (filter(localizedText2.Key, localizedText2.Value) && --num == num2)
343 | - {
344 | - return localizedText2.Value;
345 | - }
346 | - }
347 | - return LocalizedText.Empty;
348 | - }
349 | -
350 | - public LocalizedText RandomFromCategory(string categoryName, UnifiedRandom random = null)
351 | - {
352 | - if (!_categoryGroupedKeys.ContainsKey(categoryName))
353 | - {
354 | - return new LocalizedText(categoryName + ".RANDOM", categoryName + ".RANDOM");
355 | - }
356 | - List list = _categoryGroupedKeys[categoryName];
357 | - return GetText(categoryName + "." + list[(random ?? Main.rand).Next(list.Count)]);
358 | - }
359 | -
360 | - public LocalizedText IndexedFromCategory(string categoryName, int index)
361 | - {
362 | - if (!_categoryGroupedKeys.ContainsKey(categoryName))
363 | - {
364 | - return new LocalizedText(categoryName + ".INDEXED", categoryName + ".INDEXED");
365 | - }
366 | - List list = _categoryGroupedKeys[categoryName];
367 | - int index2 = index % list.Count;
368 | - return GetText(categoryName + "." + list[index2]);
369 | - }
370 | -
371 | - public bool Exists(string key)
372 | - {
373 | - return _localizedTexts.ContainsKey(key);
374 | - }
375 | -
376 | - public LocalizedText GetText(string key)
377 | - {
378 | - if (!_localizedTexts.ContainsKey(key))
379 | - {
380 | - return new LocalizedText(key, key);
381 | - }
382 | - return _localizedTexts[key];
383 | - }
384 | -
385 | - public string GetTextValue(string key)
386 | - {
387 | - if (_localizedTexts.ContainsKey(key))
388 | - {
389 | - return _localizedTexts[key].Value;
390 | - }
391 | - return key;
392 | - }
393 | -
394 | - public string GetTextValue(string key, object arg0)
395 | - {
396 | - if (_localizedTexts.ContainsKey(key))
397 | - {
398 | - return _localizedTexts[key].Format(arg0);
399 | - }
400 | - return key;
401 | - }
402 | -
403 | - public string GetTextValue(string key, object arg0, object arg1)
404 | - {
405 | - if (_localizedTexts.ContainsKey(key))
406 | - {
407 | - return _localizedTexts[key].Format(arg0, arg1);
408 | - }
409 | - return key;
410 | - }
411 | -
412 | - public string GetTextValue(string key, object arg0, object arg1, object arg2)
413 | - {
414 | - if (_localizedTexts.ContainsKey(key))
415 | - {
416 | - return _localizedTexts[key].Format(arg0, arg1, arg2);
417 | - }
418 | - return key;
419 | - }
420 | -
421 | - public string GetTextValue(string key, params object[] args)
422 | - {
423 | - if (_localizedTexts.ContainsKey(key))
424 | - {
425 | - return _localizedTexts[key].Format(args);
426 | - }
427 | - return key;
428 | - }
429 | -
430 | - public void SetFallbackCulture(GameCulture culture)
431 | - {
432 | - _fallbackCulture = culture;
433 | - }
434 | + public GameCulture ActiveCulture { get; private set; }
435 | +
436 | + public event LanguageChangeCallback OnLanguageChanging;
437 | +
438 | + public event LanguageChangeCallback OnLanguageChanged;
439 | +
440 | + private LanguageManager()
441 | + {
442 | + _localizedTexts[""] = LocalizedText.Empty;
443 | + }
444 | +
445 | + public int GetCategorySize(string name)
446 | + {
447 | + if (_categoryGroupedKeys.ContainsKey(name))
448 | + {
449 | + return _categoryGroupedKeys[name].Count;
450 | + }
451 | + return 0;
452 | + }
453 | +
454 | + public void SetLanguage(int legacyId)
455 | + {
456 | + GameCulture language = GameCulture.FromLegacyId(legacyId);
457 | + SetLanguage(language);
458 | + }
459 | +
460 | + public void SetLanguage(string cultureName)
461 | + {
462 | + GameCulture language = GameCulture.FromName(cultureName);
463 | + SetLanguage(language);
464 | + }
465 | +
466 | + public int EstimateWordCount()
467 | + {
468 | + int num = 0;
469 | + foreach (string key in _localizedTexts.Keys)
470 | + {
471 | + string textValue = GetTextValue(key);
472 | + textValue.Replace(",", "").Replace(".", "").Replace("\"", "")
473 | + .Trim();
474 | + string[] array = textValue.Split(new char[1] { ' ' });
475 | + string[] array2 = textValue.Split(new char[1] { ' ' });
476 | + if (array.Length != array2.Length)
477 | + {
478 | + break;
479 | + }
480 | + string[] array3 = array;
481 | + foreach (string text in array3)
482 | + {
483 | + if (!string.IsNullOrWhiteSpace(text) && text.Length >= 1)
484 | + {
485 | + num++;
486 | + }
487 | + }
488 | + }
489 | + return num;
490 | + }
491 | +
492 | + private void SetAllTextValuesToKeys()
493 | + {
494 | + foreach (KeyValuePair localizedText in _localizedTexts)
495 | + {
496 | + localizedText.Value.SetValue(localizedText.Key);
497 | + }
498 | + }
499 | +
500 | + private string[] GetLanguageFilesForCulture(GameCulture culture)
501 | + {
502 | + Assembly.GetExecutingAssembly();
503 | + string[] names = ["", ".Game", ".Items", ".Legacy", ".NPCs", ".Projectiles", ".Town"];
504 | + return names.Select(x => $"Terraria.Localization.Content.{culture.Name}{x}.json").ToArray();
505 | + }
506 | +
507 | + public void SetLanguage(GameCulture culture)
508 | + {
509 | + if (ActiveCulture != culture)
510 | + {
511 | + if (culture != _fallbackCulture && ActiveCulture != _fallbackCulture)
512 | + {
513 | + SetAllTextValuesToKeys();
514 | + LoadLanguage(_fallbackCulture);
515 | + }
516 | + LoadLanguage(culture);
517 | + ActiveCulture = culture;
518 | + Thread.CurrentThread.CurrentCulture = culture.CultureInfo;
519 | + Thread.CurrentThread.CurrentUICulture = culture.CultureInfo;
520 | + if (this.OnLanguageChanged != null)
521 | + {
522 | + this.OnLanguageChanged(this);
523 | + }
524 | + _ = FontAssets.DeathText;
525 | + }
526 | + }
527 | +
528 | + private void LoadLanguage(GameCulture culture, bool processCopyCommands = true)
529 | + {
530 | + LoadFilesForCulture(culture);
531 | + if (this.OnLanguageChanging != null)
532 | + {
533 | + this.OnLanguageChanging(this);
534 | + }
535 | + if (processCopyCommands)
536 | + {
537 | + ProcessCopyCommandsInTexts();
538 | + }
539 | + ChatInitializer.PrepareAliases();
540 | + }
541 | +
542 | + private void LoadFilesForCulture(GameCulture culture)
543 | + {
544 | + string[] languageFilesForCulture = GetLanguageFilesForCulture(culture);
545 | + foreach (string text in languageFilesForCulture)
546 | + {
547 | + try
548 | + {
549 | + string text2 = Utils.ReadEmbeddedResource(text);
550 | + if (text2 == null || text2.Length < 2)
551 | + {
552 | + throw new FormatException();
553 | + }
554 | + LoadLanguageFromFileTextJson(text2, canCreateCategories: true);
555 | + }
556 | + catch (Exception)
557 | + {
558 | + if (Debugger.IsAttached)
559 | + {
560 | + Debugger.Break();
561 | + }
562 | + Console.WriteLine("Failed to load language file: " + text);
563 | + break;
564 | + }
565 | + }
566 | + }
567 | +
568 | + private void ProcessCopyCommandsInTexts()
569 | + {
570 | + Regex regex = new Regex("{\\$(\\w+\\.\\w+)}", RegexOptions.Compiled);
571 | + foreach (KeyValuePair localizedText in _localizedTexts)
572 | + {
573 | + LocalizedText value = localizedText.Value;
574 | + for (int i = 0; i < 100; i++)
575 | + {
576 | + string text = regex.Replace(value.Value, (Match match) => GetTextValue(match.Groups[1].ToString()));
577 | + if (text == value.Value)
578 | + {
579 | + break;
580 | + }
581 | + value.SetValue(text);
582 | + }
583 | + }
584 | + }
585 | +
586 | + public void UseSources(List sourcesFromLowestToHighest)
587 | + {
588 | + string name = ActiveCulture.Name;
589 | + char directorySeparatorChar = Path.DirectorySeparatorChar;
590 | + string assetNameStart = ("Localization" + directorySeparatorChar + name).ToLower();
591 | + LoadLanguage(ActiveCulture, processCopyCommands: false);
592 | + foreach (IContentSource item in sourcesFromLowestToHighest)
593 | + {
594 | + foreach (string item2 in item.GetAllAssetsStartingWith(assetNameStart))
595 | + {
596 | + string extension = item.GetExtension(item2);
597 | + if (!(extension == ".json") && !(extension == ".csv"))
598 | + {
599 | + continue;
600 | + }
601 | + using Stream stream = item.OpenStream(item2);
602 | + using StreamReader streamReader = new StreamReader(stream);
603 | + string fileText = streamReader.ReadToEnd();
604 | + if (extension == ".json")
605 | + {
606 | + LoadLanguageFromFileTextJson(fileText, canCreateCategories: false);
607 | + }
608 | + if (extension == ".csv")
609 | + {
610 | + LoadLanguageFromFileTextCsv(fileText);
611 | + }
612 | + }
613 | + }
614 | + ProcessCopyCommandsInTexts();
615 | + ChatInitializer.PrepareAliases();
616 | + }
617 | +
618 | + public void LoadLanguageFromFileTextCsv(string fileText)
619 | + {
620 | + using TextReader reader = new StringReader(fileText);
621 | + using CsvReader csvReader = new CsvReader(reader);
622 | + csvReader.Configuration.HasHeaderRecord = true;
623 | + if (!csvReader.ReadHeader())
624 | + {
625 | + return;
626 | + }
627 | + string[] fieldHeaders = csvReader.FieldHeaders;
628 | + int num = -1;
629 | + int num2 = -1;
630 | + for (int i = 0; i < fieldHeaders.Length; i++)
631 | + {
632 | + string text = fieldHeaders[i].ToLower();
633 | + if (text == "translation")
634 | + {
635 | + num2 = i;
636 | + }
637 | + if (text == "key")
638 | + {
639 | + num = i;
640 | + }
641 | + }
642 | + if (num == -1 || num2 == -1)
643 | + {
644 | + return;
645 | + }
646 | + int num3 = Math.Max(num, num2) + 1;
647 | + while (csvReader.Read())
648 | + {
649 | + string[] currentRecord = csvReader.CurrentRecord;
650 | + if (currentRecord.Length >= num3)
651 | + {
652 | + string text2 = currentRecord[num];
653 | + string value = currentRecord[num2];
654 | + if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(value) && _localizedTexts.ContainsKey(text2))
655 | + {
656 | + _localizedTexts[text2].SetValue(value);
657 | + }
658 | + }
659 | + }
660 | + }
661 | +
662 | + public void LoadLanguageFromFileTextJson(string fileText, bool canCreateCategories)
663 | + {
664 | + foreach (KeyValuePair> item in JsonConvert.DeserializeObject>>(fileText))
665 | + {
666 | + _ = item.Key;
667 | + foreach (KeyValuePair item2 in item.Value)
668 | + {
669 | + string key = item.Key + "." + item2.Key;
670 | + if (_localizedTexts.ContainsKey(key))
671 | + {
672 | + _localizedTexts[key].SetValue(item2.Value);
673 | + }
674 | + else if (canCreateCategories)
675 | + {
676 | + _localizedTexts.Add(key, new LocalizedText(key, item2.Value));
677 | + if (!_categoryGroupedKeys.ContainsKey(item.Key))
678 | + {
679 | + _categoryGroupedKeys.Add(item.Key, new List());
680 | + }
681 | + _categoryGroupedKeys[item.Key].Add(item2.Key);
682 | + }
683 | + }
684 | + }
685 | + }
686 | +
687 | + [Conditional("DEBUG")]
688 | + private void ValidateAllCharactersContainedInFont(DynamicSpriteFont font)
689 | + {
690 | + if (font == null)
691 | + {
692 | + return;
693 | + }
694 | + string text = "";
695 | + foreach (LocalizedText value2 in _localizedTexts.Values)
696 | + {
697 | + string value = value2.Value;
698 | + for (int i = 0; i < value.Length; i++)
699 | + {
700 | + char c = value[i];
701 | + if (!font.IsCharacterSupported(c))
702 | + {
703 | + text = text + value2.Key + ", " + c.ToString() + ", " + (int)c + "\n";
704 | + }
705 | + }
706 | + }
707 | + }
708 | +
709 | + public LocalizedText[] FindAll(Regex regex)
710 | + {
711 | + int num = 0;
712 | + foreach (KeyValuePair localizedText in _localizedTexts)
713 | + {
714 | + if (regex.IsMatch(localizedText.Key))
715 | + {
716 | + num++;
717 | + }
718 | + }
719 | + LocalizedText[] array = new LocalizedText[num];
720 | + int num2 = 0;
721 | + foreach (KeyValuePair localizedText2 in _localizedTexts)
722 | + {
723 | + if (regex.IsMatch(localizedText2.Key))
724 | + {
725 | + array[num2] = localizedText2.Value;
726 | + num2++;
727 | + }
728 | + }
729 | + return array;
730 | + }
731 | +
732 | + public LocalizedText[] FindAll(LanguageSearchFilter filter)
733 | + {
734 | + LinkedList linkedList = new LinkedList();
735 | + foreach (KeyValuePair localizedText in _localizedTexts)
736 | + {
737 | + if (filter(localizedText.Key, localizedText.Value))
738 | + {
739 | + linkedList.AddLast(localizedText.Value);
740 | + }
741 | + }
742 | + return linkedList.ToArray();
743 | + }
744 | +
745 | + public LocalizedText SelectRandom(LanguageSearchFilter filter, UnifiedRandom random = null)
746 | + {
747 | + int num = 0;
748 | + foreach (KeyValuePair localizedText in _localizedTexts)
749 | + {
750 | + if (filter(localizedText.Key, localizedText.Value))
751 | + {
752 | + num++;
753 | + }
754 | + }
755 | + int num2 = (random ?? Main.rand).Next(num);
756 | + foreach (KeyValuePair localizedText2 in _localizedTexts)
757 | + {
758 | + if (filter(localizedText2.Key, localizedText2.Value) && --num == num2)
759 | + {
760 | + return localizedText2.Value;
761 | + }
762 | + }
763 | + return LocalizedText.Empty;
764 | + }
765 | +
766 | + public LocalizedText RandomFromCategory(string categoryName, UnifiedRandom random = null)
767 | + {
768 | + if (!_categoryGroupedKeys.ContainsKey(categoryName))
769 | + {
770 | + return new LocalizedText(categoryName + ".RANDOM", categoryName + ".RANDOM");
771 | + }
772 | + List list = _categoryGroupedKeys[categoryName];
773 | + return GetText(categoryName + "." + list[(random ?? Main.rand).Next(list.Count)]);
774 | + }
775 | +
776 | + public LocalizedText IndexedFromCategory(string categoryName, int index)
777 | + {
778 | + if (!_categoryGroupedKeys.ContainsKey(categoryName))
779 | + {
780 | + return new LocalizedText(categoryName + ".INDEXED", categoryName + ".INDEXED");
781 | + }
782 | + List list = _categoryGroupedKeys[categoryName];
783 | + int index2 = index % list.Count;
784 | + return GetText(categoryName + "." + list[index2]);
785 | + }
786 | +
787 | + public bool Exists(string key)
788 | + {
789 | + return _localizedTexts.ContainsKey(key);
790 | + }
791 | +
792 | + public LocalizedText GetText(string key)
793 | + {
794 | + if (!_localizedTexts.ContainsKey(key))
795 | + {
796 | + return new LocalizedText(key, key);
797 | + }
798 | + return _localizedTexts[key];
799 | + }
800 | +
801 | + public string GetTextValue(string key)
802 | + {
803 | + if (_localizedTexts.ContainsKey(key))
804 | + {
805 | + return _localizedTexts[key].Value;
806 | + }
807 | + return key;
808 | + }
809 | +
810 | + public string GetTextValue(string key, object arg0)
811 | + {
812 | + if (_localizedTexts.ContainsKey(key))
813 | + {
814 | + return _localizedTexts[key].Format(arg0);
815 | + }
816 | + return key;
817 | + }
818 | +
819 | + public string GetTextValue(string key, object arg0, object arg1)
820 | + {
821 | + if (_localizedTexts.ContainsKey(key))
822 | + {
823 | + return _localizedTexts[key].Format(arg0, arg1);
824 | + }
825 | + return key;
826 | + }
827 | +
828 | + public string GetTextValue(string key, object arg0, object arg1, object arg2)
829 | + {
830 | + if (_localizedTexts.ContainsKey(key))
831 | + {
832 | + return _localizedTexts[key].Format(arg0, arg1, arg2);
833 | + }
834 | + return key;
835 | + }
836 | +
837 | + public string GetTextValue(string key, params object[] args)
838 | + {
839 | + if (_localizedTexts.ContainsKey(key))
840 | + {
841 | + return _localizedTexts[key].Format(args);
842 | + }
843 | + return key;
844 | + }
845 | +
846 | + public void SetFallbackCulture(GameCulture culture)
847 | + {
848 | + _fallbackCulture = culture;
849 | + }
850 | }
851 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Main.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Main.cs
2 | +++ terraria/Terraria/Main.cs
3 | @@ -12,7 +12,6 @@
4 | using System.Text.RegularExpressions;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | -using System.Windows.Forms;
8 | using Microsoft.Win32;
9 | using Microsoft.Xna.Framework;
10 | using Microsoft.Xna.Framework.Content;
11 | @@ -24,7 +23,6 @@
12 | using ReLogic.Graphics;
13 | using ReLogic.Localization.IME;
14 | using ReLogic.OS;
15 | -using ReLogic.Peripherals.RGB;
16 | using ReLogic.Utilities;
17 | using Terraria.Achievements;
18 | using Terraria.Audio;
19 | @@ -77,6 +75,8 @@
20 |
21 | public class Main : Game
22 | {
23 | + public static int realWindowWidth;
24 | + public static int realWindowHeight;
25 | public delegate void OnPlayerSelected(PlayerFileData player);
26 |
27 | public static class CurrentFrameFlags
28 | @@ -282,10 +282,6 @@
29 |
30 | public static Main instance;
31 |
32 | - public static ChromaEngine Chroma;
33 | -
34 | - public static ChromaHotkeyPainter ChromaPainter;
35 | -
36 | public static Camera Camera = new Camera();
37 |
38 | public static IPlayerRenderer PlayerRenderer = new LegacyPlayerRenderer();
39 | @@ -747,9 +743,9 @@
40 |
41 | public static int desiredWorldTilesUpdateRate = 1;
42 |
43 | - public static int maxScreenW = 1920;
44 | + public static int maxScreenW = 9999;
45 |
46 | - public static int maxScreenH = 1200;
47 | + public static int maxScreenH = 999;
48 |
49 | public static int minScreenW = 800;
50 |
51 | @@ -3427,21 +3423,6 @@
52 |
53 | private static void TrySupporting8K()
54 | {
55 | - if (!Platform.IsWindows)
56 | - {
57 | - return;
58 | - }
59 | - instance.ReleaseTargets();
60 | - Type type = Assembly.GetAssembly(typeof(GraphicsProfile)).GetType("Microsoft.Xna.Framework.Graphics.ProfileCapabilities", throwOnError: true);
61 | - if (type != null)
62 | - {
63 | - FieldInfo field = type.GetField("MaxTextureSize", BindingFlags.Instance | BindingFlags.NonPublic);
64 | - FieldInfo field2 = type.GetField("HiDef", BindingFlags.Static | BindingFlags.NonPublic);
65 | - if (field != null && field2 != null)
66 | - {
67 | - field.SetValue(field2.GetValue(null), 8192);
68 | - }
69 | - }
70 | }
71 |
72 | public static void AnglerQuestSwap()
73 | @@ -3906,24 +3887,6 @@
74 | }
75 | PendingBorderlessState = screenBorderless;
76 | screenBorderlessPendingResizes = (screenBorderless ? 6 : 0);
77 | - if (Platform.IsWindows && !dedServ)
78 | - {
79 | - Form form = Control.FromHandle(instance.Window.Handle);
80 | - if (screenBorderless)
81 | - {
82 | - SetBorderlessFormStyle(form);
83 | - }
84 | - else if (screenMaximized)
85 | - {
86 | - form.FormBorderStyle = FormBorderStyle.Sizable;
87 | - form.WindowState = FormWindowState.Maximized;
88 | - }
89 | - else
90 | - {
91 | - form.FormBorderStyle = FormBorderStyle.Sizable;
92 | - }
93 | - form.BringToFront();
94 | - }
95 | int currentValue2 = graphics.PreferredBackBufferWidth;
96 | int currentValue3 = graphics.PreferredBackBufferHeight;
97 | configuration.Get("DisplayWidth", ref currentValue2);
98 | @@ -4277,10 +4240,6 @@
99 | {
100 | terrariasFixedTiming = binaryReader.ReadBoolean();
101 | }
102 | - if (num >= 91 && binaryReader.ReadBoolean() && !dedServ && Platform.IsWindows)
103 | - {
104 | - Control.FromHandle(base.Window.Handle).WindowState = FormWindowState.Maximized;
105 | - }
106 | if (num >= 4)
107 | {
108 | int width = binaryReader.ReadInt32();
109 | @@ -4773,18 +4732,10 @@
110 |
111 | public void NeverSleep()
112 | {
113 | - if (Platform.IsWindows)
114 | - {
115 | - previousExecutionState = NativeMethods.SetThreadExecutionState(2147483649u);
116 | - }
117 | }
118 |
119 | public void YouCanSleepNow()
120 | {
121 | - if (Platform.IsWindows && previousExecutionState != 0)
122 | - {
123 | - NativeMethods.SetThreadExecutionState(previousExecutionState);
124 | - }
125 | }
126 |
127 | public void DedServ()
128 | @@ -4794,14 +4745,6 @@
129 | if (autoShutdown)
130 | {
131 | string lpWindowName = (Console.Title = "terraria" + rand.Next(int.MaxValue));
132 | - if (Platform.IsWindows)
133 | - {
134 | - IntPtr intPtr = FindWindow(null, lpWindowName);
135 | - if (intPtr != IntPtr.Zero)
136 | - {
137 | - ShowWindow(intPtr, 0);
138 | - }
139 | - }
140 | }
141 | else
142 | {
143 | @@ -5757,8 +5700,10 @@
144 | }
145 | }
146 |
147 | - public Main()
148 | + public Main(int width, int height)
149 | {
150 | + realWindowWidth = width;
151 | + realWindowHeight = height;
152 | instance = this;
153 | UnpausedUpdateSeed = (ulong)Guid.NewGuid().GetHashCode();
154 | base.Exiting += Main_Exiting;
155 | @@ -5910,7 +5855,6 @@
156 | MinimapFrameManagerInstance.BindTo(preferences);
157 | BigBossProgressBar.BindTo(preferences);
158 | ResourceSetsManager.BindTo(preferences);
159 | - ChromaInitializer.BindTo(preferences);
160 | Terraria.Graphics.Effects.Filters.Scene.BindTo(preferences);
161 | if (SocialAPI.Cloud != null)
162 | {
163 | @@ -6065,12 +6009,6 @@
164 | WorldGen.EveryTileFrame();
165 | player[myPlayer].Spawn(PlayerSpawnContext.SpawningIntoWorld);
166 | }
167 | - else if (Platform.IsWindows && !dedServ)
168 | - {
169 | - IntPtr systemMenu = GetSystemMenu(base.Window.Handle, bRevert: false);
170 | - int menuItemCount = GetMenuItemCount(systemMenu);
171 | - RemoveMenu(systemMenu, menuItemCount - 1, 1024);
172 | - }
173 | if (!dedServ)
174 | {
175 | SoundID.FillAccessMap();
176 | @@ -9829,9 +9767,6 @@
177 | _achievementAdvisor = new AchievementAdvisor();
178 | OpenRecent();
179 | UILinksInitializer.Load();
180 | - Chroma = new ChromaEngine();
181 | - ChromaPainter = new ChromaHotkeyPainter();
182 | - ChromaPainter.CollectBoundKeys();
183 | CacheSupportedDisplaySizes();
184 | if (autoJoin)
185 | {
186 | @@ -9917,7 +9852,6 @@
187 | }
188 | AssetSourceController.UseResourcePacks(AssetInitializer.CreateResourcePackList(base.Services));
189 | AssetInitializer.LoadSplashAssets(asyncLoadForSounds: true);
190 | - ChromaInitializer.Load();
191 | _gameContentLoadProcess = LoadContent_Deferred();
192 | }
193 |
194 | @@ -15630,8 +15564,6 @@
195 | {
196 | UpdateAudio();
197 | GlobalTimeWrappedHourly = (float)(gameTime.TotalGameTime.TotalSeconds % 3600.0);
198 | - ChromaInitializer.UpdateEvents();
199 | - Chroma.Update(GlobalTimeWrappedHourly);
200 | return;
201 | }
202 | PartySky.MultipleSkyWorkaroundFix = true;
203 | @@ -15736,8 +15668,6 @@
204 | DoUpdate_AutoSave();
205 | if (!dedServ)
206 | {
207 | - ChromaInitializer.UpdateEvents();
208 | - Chroma.Update(GlobalTimeWrappedHourly);
209 | if (superFast)
210 | {
211 | base.IsFixedTimeStep = false;
212 | @@ -15930,18 +15860,7 @@
213 | logoScale = 1f;
214 | }
215 | UpdateOldNPCShop();
216 | - hasFocus = base.IsActive;
217 | - if (Platform.IsWindows)
218 | - {
219 | - Form form = Control.FromHandle(base.Window.Handle);
220 | - bool num3 = form.WindowState == FormWindowState.Minimized;
221 | - bool flag = Form.ActiveForm == form;
222 | - hasFocus |= flag;
223 | - if (num3)
224 | - {
225 | - hasFocus = false;
226 | - }
227 | - }
228 | + hasFocus = true;
229 | if (!hasFocus && netMode == 0)
230 | {
231 | if (!Platform.IsOSX)
232 | @@ -16101,10 +16020,6 @@
233 | {
234 | DoUpdateInWorld(_worldUpdateTimeTester);
235 | }
236 | - if (netMode != 2)
237 | - {
238 | - ChromaPainter.Update();
239 | - }
240 | }
241 |
242 | internal static void UpdateCreativeGameModeOverride()
243 | @@ -44855,7 +44770,6 @@
244 |
245 | private static void TryDisposingEverything()
246 | {
247 | - ChromaInitializer.DisableAllDeviceGroups();
248 | CaptureManager.Instance.Dispose();
249 | audioSystem.Dispose();
250 | }
251 | @@ -49159,13 +49073,10 @@
252 | private static bool IsBorderlessDisplayAvailable()
253 | {
254 | bool result = false;
255 | - if (Platform.IsWindows)
256 | - {
257 | - result = true;
258 | - }
259 | return result;
260 | }
261 |
262 | + /*
263 | private static void SetDisplayModeAsBorderless(ref int width, ref int height, Form form)
264 | {
265 | if (screenBorderless && !graphics.IsFullScreen && screenBorderlessPendingResizes > 0)
266 | @@ -49196,6 +49107,7 @@
267 | form.Location = new System.Drawing.Point(0, 0);
268 | form.FormBorderStyle = FormBorderStyle.None;
269 | }
270 | + */
271 |
272 | public static void OpenCharacterSelectUI()
273 | {
274 | @@ -62937,111 +62849,69 @@
275 |
276 | public static void SetDisplayMode(int width, int height, bool fullscreen)
277 | {
278 | + width = realWindowWidth;
279 | + height = realWindowHeight;
280 | +
281 | bool flag = false;
282 | - Form form = null;
283 | - if (Platform.IsWindows)
284 | - {
285 | - form = Control.FromHandle(instance.Window.Handle);
286 | - screenMaximized = form.WindowState == FormWindowState.Maximized;
287 | - if (screenBorderless && screenMaximized && !graphics.IsFullScreen)
288 | - {
289 | - screenMaximized = false;
290 | - form.WindowState = FormWindowState.Normal;
291 | - }
292 | - flag = form.FormBorderStyle == FormBorderStyle.None;
293 | - }
294 | - else
295 | - {
296 | - screenMaximized = false;
297 | - }
298 | + screenMaximized = false;
299 | bool flag2 = false;
300 | int num3;
301 | int num4;
302 | - if (screenBorderless || screenMaximized || graphics.IsFullScreen || fullscreen)
303 | - {
304 | - bool flag3 = false;
305 | - if (PlayerInput.SteamDeckIsUsed)
306 | - {
307 | - flag3 = true;
308 | - if (!fullscreen && !graphics.IsFullScreen)
309 | - {
310 | - width = 1280;
311 | - height = 800;
312 | - TryPickingDefaultUIScale(800f);
313 | - }
314 | - }
315 | - if (Platform.IsWindows)
316 | - {
317 | - form.MinimumSize = new Size(0, 0);
318 | - if (!fullscreen && !flag3)
319 | - {
320 | - SetDisplayModeAsBorderless(ref width, ref height, form);
321 | - }
322 | - }
323 | - if (width > maxScreenW)
324 | - {
325 | - float num = (float)height / (float)width;
326 | - width = maxScreenW;
327 | - height = (int)(num * (float)width);
328 | - }
329 | - if (height > maxScreenH)
330 | - {
331 | - float num2 = (float)width / (float)height;
332 | - height = maxScreenH;
333 | - width = (int)(num2 * (float)height);
334 | - }
335 | - PlayerInput.RawMouseScale = new Vector2((float)width / (float)instance.Window.ClientBounds.Width, (float)height / (float)instance.Window.ClientBounds.Height);
336 | - if (!graphics.IsFullScreen)
337 | - {
338 | - num3 = Math.Max(graphics.PreferredBackBufferWidth, graphics.GraphicsDevice.Viewport.Width);
339 | - num4 = Math.Max(graphics.PreferredBackBufferHeight, graphics.GraphicsDevice.Viewport.Height);
340 | - if (num3 != graphics.PreferredBackBufferWidth || num4 != graphics.PreferredBackBufferHeight)
341 | - {
342 | - flag2 = true;
343 | - }
344 | - }
345 | - else
346 | - {
347 | - num3 = graphics.PreferredBackBufferWidth;
348 | - num4 = graphics.PreferredBackBufferHeight;
349 | - }
350 | - }
351 | - else
352 | - {
353 | + // if (screenBorderless || screenMaximized || graphics.IsFullScreen || fullscreen)
354 | + // {
355 | + // bool flag3 = false;
356 | + // if (PlayerInput.SteamDeckIsUsed)
357 | + // {
358 | + // flag3 = true;
359 | + // if (!fullscreen && !graphics.IsFullScreen)
360 | + // {
361 | + // width = 1280;
362 | + // height = 800;
363 | + // TryPickingDefaultUIScale(800f);
364 | + // }
365 | + // }
366 | + // if (width > maxScreenW)
367 | + // {
368 | + // float num = (float)height / (float)width;
369 | + // width = maxScreenW;
370 | + // height = (int)(num * (float)width);
371 | + // }
372 | + // if (height > maxScreenH)
373 | + // {
374 | + // float num2 = (float)width / (float)height;
375 | + // height = maxScreenH;
376 | + // width = (int)(num2 * (float)height);
377 | + // }
378 | + // PlayerInput.RawMouseScale = new Vector2((float)width / (float)instance.Window.ClientBounds.Width, (float)height / (float)instance.Window.ClientBounds.Height);
379 | + // // if (!graphics.IsFullScreen)
380 | + // // {
381 | + // num3 = Math.Max(graphics.PreferredBackBufferWidth, graphics.GraphicsDevice.Viewport.Width);
382 | + // num4 = Math.Max(graphics.PreferredBackBufferHeight, graphics.GraphicsDevice.Viewport.Height);
383 | + // if (num3 != graphics.PreferredBackBufferWidth || num4 != graphics.PreferredBackBufferHeight)
384 | + // {
385 | + // flag2 = true;
386 | + // }
387 | + // // }
388 | + // // else
389 | + // // {
390 | + // // num3 = graphics.PreferredBackBufferWidth;
391 | + // // num4 = graphics.PreferredBackBufferHeight;
392 | + // // }
393 | + // }
394 | + // else
395 | + // {
396 | PlayerInput.RawMouseScale = Vector2.One;
397 | - if (Platform.IsWindows)
398 | - {
399 | - form.MinimumSize = new Size(minScreenW, minScreenH);
400 | - if (flag)
401 | - {
402 | - width = displayWidth[0];
403 | - height = displayHeight[0];
404 | - }
405 | - }
406 | width = Math.Min(width, maxScreenW);
407 | height = Math.Min(height, maxScreenH);
408 | num3 = graphics.GraphicsDevice.Viewport.Width;
409 | num4 = graphics.GraphicsDevice.Viewport.Height;
410 | flag2 = graphics.PreferredBackBufferWidth != graphics.GraphicsDevice.Viewport.Width || graphics.PreferredBackBufferHeight != graphics.GraphicsDevice.Viewport.Height;
411 | - }
412 | - if (Platform.IsWindows && !fullscreen && !flag2)
413 | - {
414 | - if (form.ClientSize.Width < graphics.PreferredBackBufferWidth)
415 | - {
416 | - width = form.ClientSize.Width;
417 | - flag2 = true;
418 | - }
419 | - if (form.ClientSize.Height < graphics.PreferredBackBufferHeight)
420 | - {
421 | - height = form.ClientSize.Height;
422 | - flag2 = true;
423 | - }
424 | - }
425 | + // }
426 | width &= 0x7FFFFFFE;
427 | height &= 0x7FFFFFFE;
428 | - width = Math.Max(width, minScreenW);
429 | - height = Math.Max(height, minScreenH);
430 | - if (graphics.IsFullScreen != fullscreen)
431 | + width = realWindowWidth;
432 | + height = realWindowHeight;
433 | + if (graphics.IsFullScreen != fullscreen)
434 | {
435 | graphics.PreferredBackBufferWidth = width;
436 | graphics.PreferredBackBufferHeight = height;
437 | @@ -63069,20 +62939,6 @@
438 | PendingResolutionWidth = screenWidth;
439 | PendingResolutionHeight = screenHeight;
440 | PlayerInput.CacheOriginalScreenDimensions();
441 | - if (Platform.IsWindows && !fullscreen)
442 | - {
443 | - if (screenBorderless)
444 | - {
445 | - ApplyBorderlessResolution(form);
446 | - form.FormBorderStyle = FormBorderStyle.None;
447 | - }
448 | - else
449 | - {
450 | - form.FormBorderStyle = FormBorderStyle.Sizable;
451 | - }
452 | - form.SendToBack();
453 | - form.BringToFront();
454 | - }
455 | Lighting.Initialize();
456 | if (!drawToScreen && !_isResizingAndRemakingTargets)
457 | {
458 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/NPC.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/NPC.cs
2 | +++ terraria/Terraria/NPC.cs
3 | @@ -14,7 +14,6 @@
4 | using Terraria.GameContent.Drawing;
5 | using Terraria.GameContent.Events;
6 | using Terraria.GameContent.ItemDropRules;
7 | -using Terraria.GameContent.RGB;
8 | using Terraria.GameContent.Tile_Entities;
9 | using Terraria.GameContent.UI;
10 | using Terraria.Graphics.CameraModifiers;
11 | @@ -1379,7 +1378,7 @@
12 | {
13 | highestTierBossOrEvent = 398;
14 | }
15 | - CommonConditions.Boss.HighestTierBossOrEvent = highestTierBossOrEvent;
16 | + //CommonConditions.Boss.HighestTierBossOrEvent = highestTierBossOrEvent;
17 | }
18 |
19 | public void SpawnWithHigherTime(int timeMult)
20 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Net/Sockets/TcpSocket.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Net/Sockets/TcpSocket.cs
2 | +++ terraria/Terraria/Net/Sockets/TcpSocket.cs
3 | @@ -30,18 +30,22 @@
4 |
5 | public TcpSocket()
6 | {
7 | + /*
8 | _connection = new TcpClient
9 | {
10 | NoDelay = true
11 | - };
12 | + };
13 | + */
14 | }
15 |
16 | public TcpSocket(TcpClient tcpClient)
17 | {
18 | + /*
19 | _connection = tcpClient;
20 | _connection.NoDelay = true;
21 | IPEndPoint iPEndPoint = (IPEndPoint)tcpClient.Client.RemoteEndPoint;
22 | _remoteAddress = new TcpAddress(iPEndPoint.Address, iPEndPoint.Port);
23 | + */
24 | }
25 |
26 | void ISocket.Close()
27 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Player.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Player.cs
2 | +++ terraria/Terraria/Player.cs
3 | @@ -35,6 +35,7 @@
4 | using Terraria.UI.Gamepad;
5 | using Terraria.Utilities;
6 | using Terraria.WorldBuilding;
7 | +using System.Linq;
8 |
9 | namespace Terraria;
10 |
11 | @@ -51191,19 +51192,33 @@
12 | {
13 | FileUtilities.Copy(path, path + ".bak", isCloudSave);
14 | }
15 | - RijndaelManaged rijndaelManaged = new RijndaelManaged();
16 | - using Stream stream = (isCloudSave ? ((Stream)new MemoryStream(2000)) : ((Stream)new FileStream(path, FileMode.Create)));
17 | - using CryptoStream cryptoStream = new CryptoStream(stream, rijndaelManaged.CreateEncryptor(ENCRYPTION_KEY, ENCRYPTION_KEY), CryptoStreamMode.Write);
18 | - using BinaryWriter binaryWriter = new BinaryWriter(cryptoStream);
19 | + using Stream stream = ((Stream)new MemoryStream(2000));
20 | + using BinaryWriter binaryWriter = new BinaryWriter(stream);
21 | binaryWriter.Write(279);
22 | playerFile.Metadata.Write(binaryWriter);
23 | Serialize(playerFile, player, binaryWriter);
24 | binaryWriter.Flush();
25 | - cryptoStream.FlushFinalBlock();
26 | stream.Flush();
27 | +
28 | + byte[] plaintext = ((MemoryStream)stream).ToArray();
29 | + byte[] key = ENCRYPTION_KEY.ToArray();
30 | + byte[] iv = ENCRYPTION_KEY.ToArray();
31 | + byte[] ciphertext = new byte[plaintext.Length + 128]; // padding
32 | + int byteswritten = 0;
33 | +
34 | + unsafe {
35 | + fixed (byte *pPlaintext = plaintext, piv = iv, pdata = ciphertext) {
36 | + byteswritten = NativeCrypto.AesEncryptCbc(key, key.Length, piv, pPlaintext, plaintext.Length, pdata);
37 | + }
38 | + }
39 | +
40 | if (isCloudSave && SocialAPI.Cloud != null)
41 | {
42 | - SocialAPI.Cloud.Write(playerFile.Path, ((MemoryStream)stream).ToArray());
43 | + SocialAPI.Cloud.Write(playerFile.Path, ciphertext, byteswritten);
44 | + } else {
45 | + var fs = File.Open(path, FileMode.Create, FileAccess.Write);
46 | + fs.Write(ciphertext, 0, byteswritten);
47 | + fs.Close();
48 | }
49 | }
50 |
51 | @@ -51507,12 +51522,19 @@
52 | bool gotToReadName = false;
53 | try
54 | {
55 | - RijndaelManaged rijndaelManaged = new RijndaelManaged();
56 | - rijndaelManaged.Padding = PaddingMode.None;
57 | - using (MemoryStream stream = new MemoryStream(FileUtilities.ReadAllBytes(playerPath, cloudSave)))
58 | + byte[] ciphertext = FileUtilities.ReadAllBytes(playerPath, cloudSave);
59 | + byte[] plaintext = new byte[ciphertext.Length];
60 | + byte[] key = ENCRYPTION_KEY.ToArray();
61 | +
62 | + unsafe {
63 | + fixed (byte *pCiphertext = ciphertext, pPlaintext = plaintext, piv = key) {
64 | + int newlen = NativeCrypto.AesDecryptCbc(key, key.Length, piv, pCiphertext, ciphertext.Length, pPlaintext);
65 | + Array.Resize(ref plaintext, newlen);
66 | + }
67 | + }
68 | + using (MemoryStream stream = new MemoryStream(plaintext))
69 | {
70 | - using CryptoStream input = new CryptoStream(stream, rijndaelManaged.CreateDecryptor(ENCRYPTION_KEY, ENCRYPTION_KEY), CryptoStreamMode.Read);
71 | - using BinaryReader binaryReader = new BinaryReader(input);
72 | + using BinaryReader binaryReader = new BinaryReader(stream);
73 | int num = binaryReader.ReadInt32();
74 | if (num >= 135)
75 | {
76 | @@ -51536,8 +51558,10 @@
77 | playerFileData.Player = player;
78 | return playerFileData;
79 | }
80 | - catch
81 | + catch (Exception ex)
82 | {
83 | + Console.WriteLine("Failed to load player data");
84 | + Console.WriteLine(ex);
85 | }
86 | Player player2 = new Player();
87 | player2.loadStatus = 2;
88 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Social/SocialAPI.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Social/SocialAPI.cs
2 | +++ terraria/Terraria/Social/SocialAPI.cs
3 | @@ -3,7 +3,6 @@
4 | using ReLogic.OS;
5 | using Terraria.Social.Base;
6 | using Terraria.Social.Steam;
7 | -using Terraria.Social.WeGame;
8 |
9 | namespace Terraria.Social;
10 |
11 | @@ -47,9 +46,11 @@
12 | case SocialMode.Steam:
13 | LoadSteam();
14 | break;
15 | + /*
16 | case SocialMode.WeGame:
17 | LoadWeGame();
18 | break;
19 | + */
20 | }
21 | foreach (ISocialModule module in _modules)
22 | {
23 | @@ -104,9 +105,9 @@
24 | {
25 | Network = LoadModule();
26 | }
27 | - WeGameHelper.WriteDebugString("LoadSteam modules");
28 | }
29 |
30 | + /*
31 | private static void LoadWeGame()
32 | {
33 | LoadModule();
34 | @@ -123,4 +124,5 @@
35 | }
36 | WeGameHelper.WriteDebugString("LoadWeGame modules");
37 | }
38 | + */
39 | }
40 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Social/SocialMode.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Social/SocialMode.cs
2 | +++ terraria/Terraria/Social/SocialMode.cs
3 | @@ -4,5 +4,4 @@
4 | {
5 | None,
6 | Steam,
7 | - WeGame
8 | }
9 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Social/Steam/CoreSocialModule.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Social/Steam/CoreSocialModule.cs
2 | +++ terraria/Terraria/Social/Steam/CoreSocialModule.cs
3 | @@ -1,6 +1,5 @@
4 | using System;
5 | using System.Threading;
6 | -using System.Windows.Forms;
7 | using ReLogic.OS;
8 | using Steamworks;
9 | using Terraria.Localization;
10 | @@ -39,7 +38,7 @@
11 | }
12 | if (!SteamAPI.Init())
13 | {
14 | - MessageBox.Show(Language.GetTextValue("Error.LaunchFromSteam"), Language.GetTextValue("Error.Error"));
15 | + Console.WriteLine(Language.GetTextValue("Error.LaunchFromSteam"), Language.GetTextValue("Error.Error"));
16 | Environment.Exit(1);
17 | }
18 | IsSteamValid = true;
19 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Social/Steam/NetClientSocialModule.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Social/Steam/NetClientSocialModule.cs
2 | +++ terraria/Terraria/Social/Steam/NetClientSocialModule.cs
3 | @@ -4,7 +4,6 @@
4 | using Terraria.Localization;
5 | using Terraria.Net;
6 | using Terraria.Net.Sockets;
7 | -using Terraria.Social.WeGame;
8 |
9 | namespace Terraria.Social.Steam;
10 |
11 | @@ -57,7 +56,6 @@
12 | playerData.SetAsActive();
13 | Main.menuMode = 882;
14 | Main.statusText = Language.GetTextValue("Social.Joining");
15 | - WeGameHelper.WriteDebugString(" CheckParameters, lobby.join");
16 | _lobby.Join(lobbySteamId, OnLobbyEntered);
17 | });
18 | }
19 | @@ -65,7 +63,6 @@
20 |
21 | public override void LaunchLocalServer(Process process, ServerMode mode)
22 | {
23 | - WeGameHelper.WriteDebugString("LaunchLocalServer");
24 | if (_lobby.State != 0)
25 | {
26 | _lobby.Leave();
27 | @@ -155,7 +152,6 @@
28 |
29 | private void OnLobbyJoinRequest(GameLobbyJoinRequested_t result)
30 | {
31 | - WeGameHelper.WriteDebugString(" OnLobbyJoinRequest");
32 | if (_lobby.State != 0)
33 | {
34 | _lobby.Leave();
35 | @@ -176,7 +172,6 @@
36 |
37 | private void OnLobbyEntered(LobbyEnter_t result, bool failure)
38 | {
39 | - WeGameHelper.WriteDebugString(" OnLobbyEntered");
40 | SteamNetworking.AllowP2PPacketRelay(bAllow: true);
41 | SendAuthTicket(_lobby.Owner);
42 | int num = 0;
43 | @@ -221,7 +216,6 @@
44 |
45 | private void SendAuthTicket(CSteamID address)
46 | {
47 | - WeGameHelper.WriteDebugString(" SendAuthTicket");
48 | if (_authTicket == HAuthTicket.Invalid)
49 | {
50 | _authTicket = SteamUser.GetAuthSessionTicket(_authData, _authData.Length, out _authDataLength);
51 | @@ -261,13 +255,11 @@
52 |
53 | private void OnSessionConnectFail(P2PSessionConnectFail_t result)
54 | {
55 | - WeGameHelper.WriteDebugString(" OnSessionConnectFail");
56 | Close(result.m_steamIDRemote);
57 | }
58 |
59 | private void OnP2PSessionRequest(P2PSessionRequest_t result)
60 | {
61 | - WeGameHelper.WriteDebugString(" OnP2PSessionRequest");
62 | CSteamID steamIDRemote = result.m_steamIDRemote;
63 | if (_connectionStateMap.ContainsKey(steamIDRemote) && _connectionStateMap[steamIDRemote] != 0)
64 | {
65 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/UI/FancyErrorPrinter.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/UI/FancyErrorPrinter.cs
2 | +++ terraria/Terraria/UI/FancyErrorPrinter.cs
3 | @@ -2,7 +2,6 @@
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 | -using System.Windows.Forms;
8 | using ReLogic.Content;
9 | using ReLogic.OS;
10 |
11 | @@ -129,9 +128,5 @@
12 |
13 | private static void ShowTheBox(string preparedMessage)
14 | {
15 | - if (Platform.IsWindows && !Main.dedServ)
16 | - {
17 | - MessageBox.Show(preparedMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
18 | - }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Utilities/FileBrowser/NativeFileDialog.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Utilities/FileBrowser/NativeFileDialog.cs
2 | +++ terraria/Terraria/Utilities/FileBrowser/NativeFileDialog.cs
3 | @@ -6,11 +6,14 @@
4 | {
5 | public string OpenFilePanel(string title, ExtensionFilter[] extensions)
6 | {
7 | + throw new System.Exception("TODO");
8 | + /*
9 | string[] value = extensions.SelectMany((ExtensionFilter x) => x.Extensions).ToArray();
10 | if (nativefiledialog.NFD_OpenDialog(string.Join(",", value), null, out var outPath) == nativefiledialog.nfdresult_t.NFD_OKAY)
11 | {
12 | return outPath;
13 | }
14 | + */
15 | return null;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/terraria/Patches/Vanilla/Terraria/Utilities/PlatformUtilities.cs.patch:
--------------------------------------------------------------------------------
1 | --- terraria/Decompiled/Terraria/Utilities/PlatformUtilities.cs
2 | +++ terraria/Terraria/Utilities/PlatformUtilities.cs
3 | @@ -31,7 +31,8 @@
4 | byte[] array = new byte[width * height * 4 + 41 + 57 + 256];
5 | IntPtr intPtr = Marshal.AllocHGlobal(array.Length);
6 | IntPtr dst = SDL.SDL_RWFromMem(intPtr, array.Length);
7 | - SDL_image.IMG_SavePNG_RW(surface, dst, 1);
8 | + // TerrariaWasm TODO!!!
9 | + // SDL_image.IMG_SavePNG_RW(surface, dst, 1);
10 | SDL.SDL_FreeSurface(surface);
11 | Marshal.Copy(intPtr, array, 0, array.Length);
12 | Marshal.FreeHGlobal(intPtr);
13 |
--------------------------------------------------------------------------------
/terraria/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using System.Runtime.InteropServices.JavaScript;
6 | using System.Runtime.InteropServices;
7 | using Terraria.Initializers;
8 | using Terraria.Localization;
9 | using Terraria.Social;
10 | using System.Collections.Generic;
11 | using DepotDownloader;
12 | using SteamKit2;
13 | using QRCoder;
14 | using System.Text.RegularExpressions;
15 | using System.Linq;
16 | using SDL3;
17 |
18 | partial class JS
19 | {
20 | [JSImport("newqr", "depot.js")]
21 | public static partial void newqr(string dataurl);
22 | }
23 |
24 | partial class Program
25 | {
26 | private static void TryCreateDirectory(string path)
27 | {
28 | if (!Directory.Exists(path))
29 | Directory.CreateDirectory(path);
30 | }
31 | private static void Main()
32 | {
33 | Console.WriteLine("Hi!");
34 | }
35 |
36 | [DllImport("Emscripten")]
37 | public extern static int mount_opfs();
38 |
39 |
40 | static Terraria.Main game;
41 | public static bool firstLaunch = true;
42 |
43 | public static bool IsXna = true;
44 | public static bool IsFna = false;
45 | public static bool IsMono = true;
46 | public const bool IsDebug = false;
47 | public static bool LoadedEverything = true;
48 | public static Dictionary LaunchParameters = new Dictionary();
49 | public static string SavePath;
50 | public const string TerrariaSaveFolderPath = "libsdl/Terraria";
51 |
52 | [JSExport]
53 | internal static Task PreInit()
54 | {
55 | return Task.Run(() =>
56 | {
57 | Console.WriteLine("calling mount_opfs");
58 | int ret = mount_opfs();
59 | Console.WriteLine($"called mount_opfs: {ret}");
60 | if (ret != 0)
61 | {
62 | throw new Exception("Failed to mount OPFS");
63 | }
64 | Directory.CreateSymbolicLink("/Content", "/libsdl/Content");
65 | TryCreateDirectory("/libsdl/remote/");
66 |
67 | AccountSettingsStore.LoadFromFile("/libsdl/account.config");
68 | DebugLog.Enabled = false;
69 |
70 | // DebugLog.Enabled = true;
71 | // DebugLog.AddListener((category, message) =>
72 | // {
73 | // Console.WriteLine("[{0}] {1}", category, message);
74 | // });
75 |
76 |
77 | ContentDownloader.Config.RememberPassword = true;
78 | // ContentDownloader.Config.UseQrCode = HasParameter(args, "-qr");
79 |
80 | // ContentDownloader.Config.DownloadManifestOnly = HasParameter(args, "-manifest-only");
81 |
82 |
83 | ContentDownloader.Config.CellID = 0;
84 |
85 |
86 | ContentDownloader.Config.UsingFileList = true;
87 | ContentDownloader.Config.FilesToDownload = new HashSet(StringComparer.OrdinalIgnoreCase);
88 | ContentDownloader.Config.FilesToDownloadRegex = [
89 | new Regex("Content\\/.*", RegexOptions.Compiled | RegexOptions.IgnoreCase),
90 | ];
91 |
92 | ContentDownloader.Config.InstallDirectory = "/libsdl/";
93 |
94 | ContentDownloader.Config.VerifyAll = false;
95 | ContentDownloader.Config.MaxServers = 20;
96 |
97 |
98 | ContentDownloader.Config.MaxDownloads = 8;
99 | ContentDownloader.Config.MaxServers = 8;
100 | ContentDownloader.Config.LoginID = null;
101 | });
102 | }
103 |
104 | [JSExport]
105 | internal static Task Init(int width, int height)
106 | {
107 | try
108 | {
109 | Microsoft.Xna.Framework.Content.ContentTypeReaderMetaTypeManager.BackupType = typeof(ReLogic.Graphics.DynamicSpriteFontReader);
110 | SavePath = "libsdl/tsaves";
111 | game = new Terraria.Main(width, height);
112 |
113 | ThreadPool.SetMinThreads(8, 8);
114 | LanguageManager.Instance.SetLanguage(GameCulture.DefaultCulture);
115 | Terraria.Lang.InitializeLegacyLocalization();
116 | SocialAPI.Initialize(SocialMode.None);
117 | SocialAPI.Cloud = new Terraria.Social.Custom.CloudSocialModule();
118 | LaunchInitializer.LoadParameters(game);
119 |
120 | return Task.Delay(0);
121 | }
122 | catch (Exception e)
123 | {
124 | Console.Error.WriteLine("Error in Init()!!");
125 | Console.Error.WriteLine(e);
126 | return Task.FromException(e);
127 | }
128 | }
129 |
130 | [JSExport]
131 | internal static async Task InitSteamSaved()
132 | {
133 | try
134 | {
135 | if (AccountSettingsStore.Instance.LoginTokens.Keys.Count > 0)
136 | {
137 | string username = AccountSettingsStore.Instance.LoginTokens.Keys.First();
138 | if (String.IsNullOrEmpty(username)) return 1;
139 |
140 | Console.WriteLine("Using saved login token for " + username);
141 |
142 | if (ContentDownloader.InitializeSteam3(username, null))
143 | {
144 | return 0;
145 | }
146 | }
147 | }
148 | catch (Exception ex)
149 | {
150 | Console.WriteLine(ex);
151 | return 1;
152 | }
153 | return 1;
154 | }
155 |
156 | [JSExport]
157 | internal static async Task DownloadSteamCloud()
158 | {
159 | return await ContentDownloader.steam3.DownloadSteamCloud(105600, 100, "/libsdl/remote/");
160 | }
161 |
162 | [JSExport]
163 | internal static async Task InitSteam(string username, string password, bool qr)
164 | {
165 | try
166 | {
167 | ContentDownloader.Config.UseQrCode = qr;
168 | Steam3Session.qrCallback = (QRCodeData q) =>
169 | {
170 | Console.WriteLine("Got QR code data");
171 | PngByteQRCode png = new PngByteQRCode(q);
172 | byte[] bytes = png.GetGraphic(20);
173 | string dataurl = "data:image/png;base64," + Convert.ToBase64String(bytes);
174 | JS.newqr(dataurl);
175 | };
176 |
177 | if (ContentDownloader.InitializeSteam3(username, password))
178 | {
179 | return 0;
180 | }
181 | else
182 | {
183 | Console.WriteLine("Error: InitializeSteam failed");
184 | return 1;
185 | }
186 | }
187 | catch (Exception ex)
188 | {
189 | Console.WriteLine(ex);
190 | }
191 |
192 |
193 | return 1;
194 | }
195 |
196 | [JSExport]
197 | internal static async Task DownloadApp()
198 | {
199 | var depotManifestIds = new List<(uint, ulong)>();
200 | depotManifestIds.Add((105601, 8046724853517638985));
201 | // depotManifestIds.Add((731, 7617088375292372759));
202 |
203 | try
204 | {
205 | await ContentDownloader.DownloadAppAsync(105600, depotManifestIds, "public", null, null, null, false, false).ConfigureAwait(false);
206 | return 0;
207 | }
208 | catch (Exception ex)
209 | {
210 | Console.WriteLine("Could not download app: " + ex.Message);
211 | return 1;
212 | }
213 | }
214 |
215 |
216 | [JSExport]
217 | internal static Task Cleanup()
218 | {
219 | // Any cleanup for the Game - usually after game.Run() in the decompilation
220 | return Task.Delay(0);
221 | }
222 |
223 | [JSExport]
224 | internal static Task MainLoop()
225 | {
226 | try
227 | {
228 | game.RunOneFrame();
229 | }
230 | catch (Exception e)
231 | {
232 | Console.Error.WriteLine("Error in MainLoop()!");
233 | Console.Error.WriteLine(e);
234 | return Task.FromException(e);
235 | }
236 | return Task.FromResult(game.RunApplication);
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/terraria/terraria.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
6 | true
7 |
8 |
9 |
10 | net9.0
11 | true
12 | true
13 | disable
14 |
15 | Program
16 |
17 | true
18 | partial
19 |
20 | IL2026,IL2045,IL2046,IL2055,IL2057,IL2060,IL2062,IL2065,IL2067,IL2070,IL2072,IL2075,IL2077,IL2080,IL2087,IL2090,IL2091,IL2104,IL2111
21 | $(NoWarn),SYSLIB0003,SYSLIB0011,CS0168,CS0169,CS0219,CS0649,CS1522,CS1717,CS8981,CA1416,CA2022
22 |
23 | true
24 | true
25 | true
26 | true
27 | true
28 |
29 | -O3
30 | false
31 | true
32 |
33 | -sMIN_WEBGL_VERSION=2 -sWASMFS -sOFFSCREENCANVAS_SUPPORT
34 | -O3
35 | -O3
36 | web,worker
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/tools/applypatches.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | shopt -s inherit_errexit
4 |
5 | which patch &> /dev/null || {
6 | echo "Please install patch."
7 | exit 1
8 | }
9 |
10 | if ! [[ -d "terraria/Patches" ]]; then
11 | echo "Please run tools/genpatches.sh first."
12 | fi
13 |
14 | if ! [[ "$#" -eq "1" ]]; then
15 | echo "usage: bash tools/applypatches.sh "
16 | exit 1
17 | fi
18 |
19 | find "terraria/Patches/$1" -type f -name "*.patch" | while read -r patch; do
20 | file=${patch#terraria/Patches/$1/}
21 | file=${file%.patch}
22 |
23 | patch --version-control=none --no-backup-if-mismatch -p1 -i "$patch" "terraria/$file"
24 | done
25 |
--------------------------------------------------------------------------------
/tools/copydecompiled.sh:
--------------------------------------------------------------------------------
1 | cp terraria/Decompiled/app.ico public/
2 | cp -r terraria/Decompiled/{ReLogic,Terraria} terraria/
3 |
--------------------------------------------------------------------------------
/tools/decompile.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | shopt -s inherit_errexit
4 |
5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
6 |
7 | which ilspycmd &> /dev/null || {
8 | echo "Please install ilspycmd."
9 | exit 1
10 | }
11 |
12 | ILSPY="ilspycmd: 9.0.0.7889"
13 | if ! [[ "$(ilspycmd --version | head -1)" =~ ^"$ILSPY" ]]; then
14 | echo "Incorrect ilspycmd version: '$(ilspycmd --version | head -1)' != '$ILSPY'"
15 | exit 1
16 | fi
17 |
18 | terraria=${1:-}
19 | if [ ! -f "$terraria" ]; then
20 | echo "usage: bash tools/decompile.sh "
21 | echo "No terraria.exe specified or does not exist. Guessing default paths"
22 | defaults=(~/.steam/steam/steamapps/common/Terraria/Terraria.exe ~/.local/share/Steam/steamapps/common/Terraria/Terraria.exe)
23 | for terraria in "${defaults[@]}"; do
24 | if [ -f "$terraria" ]; then
25 | echo "Found terraria.exe at $terraria"
26 | break
27 | fi
28 | done
29 | if [ ! -f "$terraria" ]; then
30 | echo "Could not find terraria.exe. Please specify the path to terraria.exe as an argument."
31 | exit 1
32 | fi
33 | fi
34 |
35 |
36 | rm -r terraria/Decompiled terraria/libs terraria/{ReLogic,Terraria} || true
37 | mkdir terraria/libs
38 | cp "$(dirname "$terraria")"/{FNA,SteelSeriesEngineWrapper}.dll terraria/libs/
39 | dotnet run --project extract-relogic/extract-relogic.csproj -- "$terraria" terraria/libs/
40 | echo "if ilspy asks you to update it, do NOT LISTEN"
41 | ilspycmd --nested-directories -r terraria/libs -lv CSharp11_0 -p -o terraria/Decompiled "$terraria"
42 | ilspycmd --nested-directories -lv CSharp11_0 -p -o terraria/Decompiled terraria/libs/ReLogic.dll
43 |
44 | rm -r \
45 | terraria/Decompiled/{Terraria,ReLogic}.csproj \
46 | terraria/Decompiled/ReLogic/{{OS,Localization/IME}/Windows,Localization/IME/WindowsIme.cs,Peripherals} \
47 | terraria/Decompiled/Terraria/{Social/WeGame,Initializers/ChromaInitializer.cs,GameContent/{RGB,ChromaHotkeyPainter.cs},Net/WeGameAddress.cs,{Control,Program,LinuxLaunch}.cs}
48 |
--------------------------------------------------------------------------------
/tools/genpatches.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | shopt -s inherit_errexit
4 |
5 | which diff &> /dev/null || {
6 | echo "Please install diff."
7 | exit 1
8 | }
9 |
10 | if ! [[ -d "terraria/Decompiled" ]]; then
11 | echo "Please run tools/decompile.sh first"
12 | fi
13 |
14 | if ! [[ "$#" -eq "1" ]]; then
15 | echo "usage: bash tools/genpatches.sh "
16 | exit 1
17 | fi
18 |
19 | rm -r "terraria/Patches/$1" || true
20 |
21 | DECOMPDIR="terraria/Decompiled/"
22 | find "$DECOMPDIR"{Terraria,ReLogic} -type f -name "*.cs" | while read -r file; do
23 | file="${file#${DECOMPDIR}}"
24 | patch="terraria/Patches/$1/$file.patch"
25 |
26 |
27 | mkdir -p "$(dirname "$patch")"
28 | diff=$(diff -u --label "$DECOMPDIR$file" --label "terraria/$file" "$DECOMPDIR$file" "terraria/$file" || true)
29 |
30 | if [ -n "$diff" ]; then
31 | echo "writing diff for $file"
32 | echo "$diff" > "$patch"
33 | fi
34 | done
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | "jsx": "react",
23 | "jsxFactory": "h",
24 | "jsxFragmentFactory": "Fragment",
25 | "types": ["dreamland", "@types/wicg-file-system-access"]
26 | },
27 | "include": ["src"]
28 | }
29 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import { dreamlandPlugin } from "vite-plugin-dreamland";
3 | import { createHtmlPlugin } from "vite-plugin-html";
4 | import { writeFileSync } from "fs";
5 | import { execSync } from "child_process";
6 |
7 | export default defineConfig({
8 | plugins: [
9 | dreamlandPlugin(),
10 | createHtmlPlugin(),
11 | {
12 | name: "write-git-commit",
13 | closeBundle() {
14 | const commit = execSync("git rev-parse HEAD").toString().trim();
15 | writeFileSync("dist/MILESTONE", commit);
16 | },
17 | },
18 | ],
19 | base: "./",
20 | server: {
21 | headers: {
22 | "Cross-Origin-Embedder-Policy": "require-corp",
23 | "Cross-Origin-Opener-Policy": "same-origin",
24 | },
25 | strictPort: true,
26 | port: 5001,
27 | },
28 | build: {
29 | target: "es2022",
30 | rollupOptions: {
31 | output: {
32 | entryFileNames: `assets/[name].js`,
33 | chunkFileNames: `assets/[name].js`,
34 | assetFileNames: `assets/[name].[ext]`,
35 | },
36 | },
37 | },
38 | resolve: {
39 | alias: {
40 | fs: "rollup-plugin-node-polyfills/polyfills/empty",
41 | },
42 | },
43 | });
44 |
--------------------------------------------------------------------------------