Browse Source

Add the ability to upload a replay+WIP GBX parser

old-project-state
Dan 2 years ago
parent
commit
dd4907cde9
  1. 2
      backend/trackmania-replays/gbx-header.js
  2. 139
      backend/trackmania-replays/gbx-replay.js
  3. 9
      server.js
  4. 1
      src/app/components/season-details/season-details.component.html
  5. 3
      src/app/components/season-details/season-details.component.less
  6. 17
      src/app/components/season-details/season-details.component.ts
  7. 13
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.html
  8. 3
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.less
  9. 16
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts

2
backend/trackmania-replays/gbx-header.js

@ -61,7 +61,7 @@ class gbxHeader {
}
this.id = buff.readUInt32();
console.log(id)
console.log(this.id)
if (this.version >=6)
{

139
backend/trackmania-replays/gbx-replay.js

@ -0,0 +1,139 @@
class gbxReplay {
CHUNKS_OFFSET = 17;
HEADER_OFFSET = 21;
UNASSIGNED = 0xFFFFFFFF;
calcChunkOffset(num) {
return (((num) * 8) + this.HEADER_OFFSET)
}
isNumber(id) { return (((id) & 0xC0000000) == 0)}
isString(id) { return (((id) & 0xC0000000) != 0)}
isUnassigned(id) {return ((id) == this.UNASSIGNED)}
getIndex(id) {return ((id) & 0x3FFFFFFF)}
readIdentifier(buff, idList, outString, stringLen)
{
if ( idList.version < 3 ) {
idList.version = buff.readUInt32();
}
let id = buff.readUInt32();
if(this.isUnassigned(id)) {
return;
}
if(this.isNumber(id)){
console.log("number")
}
if(idList.version == 2) {
outString = buff.readString(stringLen)
console.log(outString)
}
if(this.isString(id) && this.getIndex(id) == 0) {
outString = buff.readString(stringLen)
console.log(outString)
}
}
parseReplayChunk(buff, chunkInfo) {
buff.seek(0+chunkInfo.dwOffset);
let version = buff.readUInt32();
let isVSK = version >= 9999 ? true : false;
let idList = { version: 0, index: 0, list:[[]]}
if( chunkInfo.dwSize <= 4) {
return;
}
if((!isVSK && version >= 3) || (isVSK && version >= 10000)) {
let outString = "";
this.readIdentifier(buff, idList, outString, 512);
}
}
parseAuthorChunk(buff, chunkInfo) {
}
parseCommunityChunk(buff, chunkInfo) {
}
parse(buff)
{
buff.seek(0+this.CHUNKS_OFFSET);
let numChunks = buff.readUInt32();
if(numChunks == 0) {
return true;
}
else if (numChunks > 0xff) {
return false;
}
let chunkId, chunkSize = 0;
let chunkOffset = this.calcChunkOffset(numChunks)
let chunkVersion = {}
let chunkCommunity = {}
let chunkAuthor = {}
for (let counter = 1; counter <= numChunks; counter++) {
chunkId = buff.readUInt32();
chunkSize = buff.readUInt32();
chunkSize &= 0x7FFFFFFF;
switch (chunkId){
case 0x03093000: // (TM)
case 0x2403F000: // (VSK, TM)
chunkVersion.dwId = chunkId;
chunkVersion.dwSize = chunkSize;
chunkVersion.dwOffset = chunkOffset;
chunkOffset += chunkSize;
break;
case 0x03093001: // (TM)
case 0x2403F001: // (VSK, TM)
chunkCommunity.dwId = chunkId;
chunkCommunity.dwSize = chunkSize;
chunkCommunity.dwOffset = chunkOffset;
chunkOffset += chunkSize;
//OutputTextFmt(hwndCtl, szOutput, _countof(szOutput), g_szChunk, dwCouter, dwChunkId, dwChunkSize);
break;
case 0x03093002: // (MP)
chunkAuthor.dwId = chunkId;
chunkAuthor.dwSize = chunkSize;
chunkAuthor.dwOffset = chunkOffset;
chunkOffset += chunkSize;
//OutputTextFmt(hwndCtl, szOutput, _countof(szOutput), g_szChunk, dwCouter, dwChunkId, dwChunkSize);
break;
default:
chunkOffset += chunkSize;
//OutputTextFmt(hwndCtl, szOutput, _countof(szOutput), g_szChunk, dwCouter, dwChunkId, dwChunkSize);
}
}
if (chunkVersion.dwSize > 0) {
this.parseReplayChunk(buff, chunkVersion);
}
if (chunkCommunity.dwSize > 0) {
this.parseCommunityChunk(buff, chunkCommunity);
}
if (chunkAuthor.dwSize > 0) {
this.parseAuthorChunk(buff, chunkAuthor);
}
}
}
module.exports = gbxReplay;

9
server.js

@ -61,6 +61,7 @@ const upload = require('./backend/routes/upload-replay');
const AdvancableBuffer = require('./backend/utilities/AdvancableBuffer.js');
const gbxHeader = require('./backend/trackmania-replays/gbx-header.js');
const gbxReplay = require('./backend/trackmania-replays/gbx-replay');
// handling CORS
app.use(cors());
@ -205,8 +206,14 @@ app.post('/api/upload-replay', upload.single('file'), (req, res) => {
fs.readFile(file.path, function(err, buffer)
{
buff = new AdvancableBuffer(buffer);
header = new gbxHeader().parse(buffer);
header = new gbxHeader()
header.parse(buff);
console.log(header);
replay = new gbxReplay();
replay.parse(buff);
})
res.status(200);
});
app.listen(3000, () => {

1
src/app/components/season-details/season-details.component.html

@ -4,6 +4,7 @@
<div *ngIf="isAuthenticated">
<button (click)="openNewRaceDialog()">New Race</button>
</div>
<button mat-raised-button class="full-width-button" color="primary" (click)="openUploadReplayDialog()">Upload Replay</button>
<mat-divider></mat-divider>
<br/>
<mat-tab-group>

3
src/app/components/season-details/season-details.component.less

@ -0,0 +1,3 @@
.full-width-button {
width: 100%;
}

17
src/app/components/season-details/season-details.component.ts

@ -5,6 +5,7 @@ import { ApiService } from '../../services/api.service';
import { Season, SeasonWeek, SeasonStandingsEntry } from '../../models/season';
import { AuthService } from 'src/app/services/auth-service';
import { NewRaceDialogComponent } from '../new-race-dialog/new-race-dialog.component';
import { UploadReplayDialogComponent } from '../upload-replay-dialog/upload-replay-dialog.component';
@Component({
selector: 'app-season-details',
@ -69,5 +70,21 @@ export class SeasonDetailsComponent {
});
}
openUploadReplayDialog(): void {
const dialogRef = this.dialog.open(UploadReplayDialogComponent, {
width: '400px',
data: { ...this.newRaceDetails }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
console.log('Follow details saved:', result);
// You can update your data or perform other actions here
} else {
console.log('Dialog canceled');
}
});
}
}

13
src/app/components/upload-replay-dialog/upload-replay-dialog.component.html

@ -1 +1,12 @@
<p>upload-replay-dialog works!</p>
<input type="file" class="file-input"
(change)="onFileSelected($event)" #fileUpload>
<div class="file-upload">
{{fileName || "No file uploaded yet."}}
<button mat-fab color="primary" class="upload-btn"
(click)="fileUpload.click()">
<mat-icon>attach_file</mat-icon>
</button>
</div>

3
src/app/components/upload-replay-dialog/upload-replay-dialog.component.less

@ -0,0 +1,3 @@
.file-input {
display: none;
}

16
src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts

@ -1,4 +1,5 @@
import { Component } from '@angular/core';
import { ApiService } from '../../services/api.service';
@Component({
selector: 'app-upload-replay-dialog',
@ -6,5 +7,20 @@ import { Component } from '@angular/core';
styleUrls: ['./upload-replay-dialog.component.less']
})
export class UploadReplayDialogComponent {
fileName = '';
constructor(private apiService: ApiService) {}
onFileSelected(event: any)
{
const file:File = event.target.files[0];
if (file)
{
this.fileName = file.name;
let formData = new FormData();
formData.append("file", file);
const upload$ = this.apiService.postReplayUpload(formData);
upload$.subscribe();
}
}
}

Loading…
Cancel
Save